thread_storm 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +5 -0
 - data/README.rdoc +31 -31
 - data/VERSION +1 -1
 - data/lib/thread_storm/active_support.rb +17 -0
 - data/lib/thread_storm/execution.rb +10 -8
 - data/lib/thread_storm/sentinel.rb +18 -0
 - data/lib/thread_storm/worker.rb +27 -14
 - data/lib/thread_storm.rb +65 -25
 - data/test/test_thread_storm.rb +110 -64
 - data/thread_storm.gemspec +3 -3
 - metadata +5 -5
 - data/lib/thread_storm/queue.rb +0 -45
 
    
        data/CHANGELOG
    CHANGED
    
    
    
        data/README.rdoc
    CHANGED
    
    | 
         @@ -11,22 +11,22 @@ Simple thread pool with a few advanced features. 
     | 
|
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
            == Example
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
               
     | 
| 
       15 
     | 
    
         
            -
               
     | 
| 
       16 
     | 
    
         
            -
               
     | 
| 
       17 
     | 
    
         
            -
               
     | 
| 
       18 
     | 
    
         
            -
               
     | 
| 
       19 
     | 
    
         
            -
               
     | 
| 
      
 14 
     | 
    
         
            +
              storm = ThreadStorm.new :size => 2
         
     | 
| 
      
 15 
     | 
    
         
            +
              storm.execute{ sleep(0.01); "a" }
         
     | 
| 
      
 16 
     | 
    
         
            +
              storm.execute{ sleep(0.01); "b" }
         
     | 
| 
      
 17 
     | 
    
         
            +
              storm.execute{ sleep(0.01); "c" }
         
     | 
| 
      
 18 
     | 
    
         
            +
              storm.join # Should return in about 0.02 seconds... ;)
         
     | 
| 
      
 19 
     | 
    
         
            +
              storm.values # ["a", "b", "c"]
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
21 
     | 
    
         
             
            == Execution state
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            You can query the state of an execution.
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
               
     | 
| 
       26 
     | 
    
         
            -
              execution =  
     | 
| 
       27 
     | 
    
         
            -
               
     | 
| 
       28 
     | 
    
         
            -
               
     | 
| 
       29 
     | 
    
         
            -
               
     | 
| 
      
 25 
     | 
    
         
            +
              storm = ThreadStorm.new :size => 2
         
     | 
| 
      
 26 
     | 
    
         
            +
              execution = storm.execute{ sleep(0.01); "a" }
         
     | 
| 
      
 27 
     | 
    
         
            +
              storm.execute{ sleep(0.01); "b" }
         
     | 
| 
      
 28 
     | 
    
         
            +
              storm.execute{ sleep(0.01); "c" }
         
     | 
| 
      
 29 
     | 
    
         
            +
              storm.join
         
     | 
| 
       30 
30 
     | 
    
         
             
              execution.started?   # true
         
     | 
| 
       31 
31 
     | 
    
         
             
              execution.finished?  # true
         
     | 
| 
       32 
32 
     | 
    
         
             
              execution.timed_out? # false
         
     | 
| 
         @@ -37,24 +37,24 @@ You can query the state of an execution. 
     | 
|
| 
       37 
37 
     | 
    
         | 
| 
       38 
38 
     | 
    
         
             
            You can restrict how long executions are allowed to run for.
         
     | 
| 
       39 
39 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
               
     | 
| 
       41 
     | 
    
         
            -
               
     | 
| 
       42 
     | 
    
         
            -
               
     | 
| 
       43 
     | 
    
         
            -
               
     | 
| 
       44 
     | 
    
         
            -
               
     | 
| 
       45 
     | 
    
         
            -
               
     | 
| 
       46 
     | 
    
         
            -
               
     | 
| 
       47 
     | 
    
         
            -
               
     | 
| 
       48 
     | 
    
         
            -
               
     | 
| 
       49 
     | 
    
         
            -
               
     | 
| 
      
 40 
     | 
    
         
            +
              storm = ThreadStorm.new :size => 2, :timeout => 0.02, :default_value => "failed"
         
     | 
| 
      
 41 
     | 
    
         
            +
              storm.execute{ sleep(0.01); "a" }
         
     | 
| 
      
 42 
     | 
    
         
            +
              storm.execute{ sleep(0.03); "b" }
         
     | 
| 
      
 43 
     | 
    
         
            +
              storm.execute{ sleep(0.01); "c" }
         
     | 
| 
      
 44 
     | 
    
         
            +
              storm.join
         
     | 
| 
      
 45 
     | 
    
         
            +
              storm.executions[1].started?   # true
         
     | 
| 
      
 46 
     | 
    
         
            +
              storm.executions[1].finished?  # true
         
     | 
| 
      
 47 
     | 
    
         
            +
              storm.executions[1].timed_out? # true
         
     | 
| 
      
 48 
     | 
    
         
            +
              storm.executions[1].duration   # ~0.02
         
     | 
| 
      
 49 
     | 
    
         
            +
              storm.executions[1].value      # "failed"
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
       51 
51 
     | 
    
         
             
            == Error handling
         
     | 
| 
       52 
52 
     | 
    
         | 
| 
       53 
53 
     | 
    
         
             
            If an execution causes an exception, it will be reraised when ThreadStorm#join (or any other method that calls ThreadStorm#join) is called, unless you pass <tt>:reraise => false</tt> to ThreadStorm#new.  The exception is stored in ThreadStorm::Execution#exception.
         
     | 
| 
       54 
54 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
               
     | 
| 
       56 
     | 
    
         
            -
              execution =  
     | 
| 
       57 
     | 
    
         
            -
               
     | 
| 
      
 55 
     | 
    
         
            +
              storm = ThreadStorm.new :size => 2, :reraise => false, :default_value => "failure"
         
     | 
| 
      
 56 
     | 
    
         
            +
              execution = storm.execute{ raise("busted"); "a" }
         
     | 
| 
      
 57 
     | 
    
         
            +
              storm.join
         
     | 
| 
       58 
58 
     | 
    
         
             
              execution.value # "failure"
         
     | 
| 
       59 
59 
     | 
    
         
             
              execution.exception # RuntimeError: busted
         
     | 
| 
       60 
60 
     | 
    
         | 
| 
         @@ -64,27 +64,27 @@ ThreadStorm#join blocks until all pending executions are done running.  It does 
     | 
|
| 
       64 
64 
     | 
    
         | 
| 
       65 
65 
     | 
    
         
             
            Sometimes it can be a pain to remember to call #shutdown, so as a convenience, you can pass a block to ThreadStorm#new and #join and #shutdown will be called for you.
         
     | 
| 
       66 
66 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
               
     | 
| 
       68 
     | 
    
         
            -
                 
     | 
| 
       69 
     | 
    
         
            -
                 
     | 
| 
       70 
     | 
    
         
            -
                 
     | 
| 
      
 67 
     | 
    
         
            +
              storm = ThreadStorm.new do |s|
         
     | 
| 
      
 68 
     | 
    
         
            +
                s.execute{ "a" }
         
     | 
| 
      
 69 
     | 
    
         
            +
                s.execute{ "b" }
         
     | 
| 
      
 70 
     | 
    
         
            +
                s.execute{ "c" }
         
     | 
| 
       71 
71 
     | 
    
         
             
              end
         
     | 
| 
       72 
72 
     | 
    
         
             
              # At this point, #join and #shutdown have been called.
         
     | 
| 
       73 
     | 
    
         
            -
               
     | 
| 
      
 73 
     | 
    
         
            +
              storm.values # ["a", "b", "c"]
         
     | 
| 
       74 
74 
     | 
    
         | 
| 
       75 
75 
     | 
    
         
             
            == Configurable timeout method
         
     | 
| 
       76 
76 
     | 
    
         | 
| 
       77 
77 
     | 
    
         
             
            <tt>Timeout.timeout</tt> is unreliable in MRI 1.8.x.  To address this, you can have ThreadStorm use an alternative implementation.
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
79 
     | 
    
         
             
             require "system_timer"
         
     | 
| 
       80 
     | 
    
         
            -
              
     | 
| 
      
 80 
     | 
    
         
            +
             storm = ThreadStorm.new :timeout_method => SystemTimer.method(:timeout) do
         
     | 
| 
       81 
81 
     | 
    
         
             
               ...
         
     | 
| 
       82 
82 
     | 
    
         
             
             end
         
     | 
| 
       83 
83 
     | 
    
         | 
| 
       84 
84 
     | 
    
         
             
            The <tt>:timeout_method</tt> option takes any callable object (i.e. <tt>responds_to?(:call)</tt>) that implements something similar to <tt>Timeout.timeout</tt> (i.e. takes the same arguments and raises <tt>Timeout::Error</tt>).
         
     | 
| 
       85 
85 
     | 
    
         | 
| 
       86 
86 
     | 
    
         
             
              require "system_timer"
         
     | 
| 
       87 
     | 
    
         
            -
               
     | 
| 
      
 87 
     | 
    
         
            +
              storm = ThreadStorm.new :timeout_method => Proc.new{ |seconds, &block| SystemTimer.timeout(seconds, &block) }
         
     | 
| 
       88 
88 
     | 
    
         
             
                ...
         
     | 
| 
       89 
89 
     | 
    
         
             
              end
         
     | 
| 
       90 
90 
     | 
    
         | 
    
        data/VERSION
    CHANGED
    
    | 
         @@ -1 +1 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            0. 
     | 
| 
      
 1 
     | 
    
         
            +
            0.5.0
         
     | 
| 
         @@ -1,5 +1,22 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Things I miss from active_support.
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            class Array #:nodoc:
         
     | 
| 
      
 4 
     | 
    
         
            +
              
         
     | 
| 
      
 5 
     | 
    
         
            +
              def separate
         
     | 
| 
      
 6 
     | 
    
         
            +
                selected = []
         
     | 
| 
      
 7 
     | 
    
         
            +
                rejected = []
         
     | 
| 
      
 8 
     | 
    
         
            +
                each do |item|
         
     | 
| 
      
 9 
     | 
    
         
            +
                  if yield(item)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    selected << item
         
     | 
| 
      
 11 
     | 
    
         
            +
                  else
         
     | 
| 
      
 12 
     | 
    
         
            +
                    rejected << item
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
                [selected, rejected]
         
     | 
| 
      
 16 
     | 
    
         
            +
              end unless method_defined?(:separate)
         
     | 
| 
      
 17 
     | 
    
         
            +
              
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
       3 
20 
     | 
    
         
             
            class Hash #:nodoc:
         
     | 
| 
       4 
21 
     | 
    
         | 
| 
       5 
22 
     | 
    
         
             
              def symbolize_keys
         
     | 
| 
         @@ -1,20 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "monitor"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            class ThreadStorm
         
     | 
| 
       2 
4 
     | 
    
         
             
              # Encapsulates a unit of work to be sent to the thread pool.
         
     | 
| 
       3 
5 
     | 
    
         
             
              class Execution
         
     | 
| 
       4 
6 
     | 
    
         
             
                attr_writer :value, :exception #:nodoc:
         
     | 
| 
       5 
7 
     | 
    
         
             
                attr_reader :args, :block, :thread #:nodoc:
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
                def initialize(args, &block) #:nodoc:
         
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(args, default_value, &block) #:nodoc:
         
     | 
| 
       8 
10 
     | 
    
         
             
                  @args = args
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @value = default_value
         
     | 
| 
       9 
12 
     | 
    
         
             
                  @block = block
         
     | 
| 
       10 
13 
     | 
    
         
             
                  @start_time = nil
         
     | 
| 
       11 
14 
     | 
    
         
             
                  @finish_time = nil
         
     | 
| 
       12 
     | 
    
         
            -
                  @value = nil
         
     | 
| 
       13 
15 
     | 
    
         
             
                  @exception = nil
         
     | 
| 
       14 
16 
     | 
    
         
             
                  @timed_out = false
         
     | 
| 
       15 
17 
     | 
    
         
             
                  @thread = nil
         
     | 
| 
       16 
     | 
    
         
            -
                  @ 
     | 
| 
       17 
     | 
    
         
            -
                  @ 
     | 
| 
      
 18 
     | 
    
         
            +
                  @lock = Monitor.new
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @cond = @lock.new_cond
         
     | 
| 
       18 
20 
     | 
    
         
             
                end
         
     | 
| 
       19 
21 
     | 
    
         | 
| 
       20 
22 
     | 
    
         
             
                def start! #:nodoc:
         
     | 
| 
         @@ -33,9 +35,9 @@ class ThreadStorm 
     | 
|
| 
       33 
35 
     | 
    
         
             
                end
         
     | 
| 
       34 
36 
     | 
    
         | 
| 
       35 
37 
     | 
    
         
             
                def finish! #:nodoc:
         
     | 
| 
       36 
     | 
    
         
            -
                  @ 
     | 
| 
      
 38 
     | 
    
         
            +
                  @lock.synchronize do
         
     | 
| 
       37 
39 
     | 
    
         
             
                    @finish_time = Time.now
         
     | 
| 
       38 
     | 
    
         
            -
                    @ 
     | 
| 
      
 40 
     | 
    
         
            +
                    @cond.signal
         
     | 
| 
       39 
41 
     | 
    
         
             
                  end
         
     | 
| 
       40 
42 
     | 
    
         
             
                end
         
     | 
| 
       41 
43 
     | 
    
         | 
| 
         @@ -70,8 +72,8 @@ class ThreadStorm 
     | 
|
| 
       70 
72 
     | 
    
         | 
| 
       71 
73 
     | 
    
         
             
                # Block until this execution has finished running. 
         
     | 
| 
       72 
74 
     | 
    
         
             
                def join
         
     | 
| 
       73 
     | 
    
         
            -
                  @ 
     | 
| 
       74 
     | 
    
         
            -
                    @ 
     | 
| 
      
 75 
     | 
    
         
            +
                  @lock.synchronize do
         
     | 
| 
      
 76 
     | 
    
         
            +
                    @cond.wait_until{ finished? }
         
     | 
| 
       75 
77 
     | 
    
         
             
                  end
         
     | 
| 
       76 
78 
     | 
    
         
             
                end
         
     | 
| 
       77 
79 
     | 
    
         | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "monitor"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class ThreadStorm
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Sentinel
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :e_cond, :p_cond
         
     | 
| 
      
 6 
     | 
    
         
            +
                
         
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @lock = Monitor.new
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @e_cond = @lock.new_cond # execute condition
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @p_cond = @lock.new_cond # pop condition
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
                
         
     | 
| 
      
 13 
     | 
    
         
            +
                def synchronize
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @lock.synchronize{ yield(@e_cond, @p_cond) }
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
                
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/thread_storm/worker.rb
    CHANGED
    
    | 
         @@ -1,12 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class ThreadStorm
         
     | 
| 
       2 
2 
     | 
    
         
             
              class Worker #:nodoc:
         
     | 
| 
       3 
     | 
    
         
            -
                attr_reader :thread
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :thread, :execution
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
                # Takes the threadsafe queue and options from the thread pool.
         
     | 
| 
       6 
     | 
    
         
            -
                def initialize(queue, options)
         
     | 
| 
       7 
     | 
    
         
            -
                  @queue 
     | 
| 
       8 
     | 
    
         
            -
                  @ 
     | 
| 
       9 
     | 
    
         
            -
                  @ 
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(queue, sentinel, options)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @queue     = queue
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @sentinel  = sentinel
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @options   = options
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @execution = nil # Current execution we're working on.
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @thread    = Thread.new(self){ |me| me.run }
         
     | 
| 
       10 
12 
     | 
    
         
             
                end
         
     | 
| 
       11 
13 
     | 
    
         | 
| 
       12 
14 
     | 
    
         
             
                def timeout
         
     | 
| 
         @@ -24,17 +26,29 @@ class ThreadStorm 
     | 
|
| 
       24 
26 
     | 
    
         | 
| 
       25 
27 
     | 
    
         
             
                # Pop an execution off the queue and process it, or pass off control to a different thread.
         
     | 
| 
       26 
28 
     | 
    
         
             
                def pop_and_process_execution
         
     | 
| 
       27 
     | 
    
         
            -
                   
     | 
| 
      
 29 
     | 
    
         
            +
                  @sentinel.synchronize do |e_cond, p_cond|
         
     | 
| 
      
 30 
     | 
    
         
            +
                    # Become idle and signal that we're idle.
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @execution = nil
         
     | 
| 
      
 32 
     | 
    
         
            +
                    e_cond.signal
         
     | 
| 
      
 33 
     | 
    
         
            +
                    
         
     | 
| 
      
 34 
     | 
    
         
            +
                    # Give up the lock and wait until there is work to do.
         
     | 
| 
      
 35 
     | 
    
         
            +
                    p_cond.wait_while{ @queue.empty? }
         
     | 
| 
      
 36 
     | 
    
         
            +
                    
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # Get the work to do (implicitly becoming busy).
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @execution = @queue.pop
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  
         
     | 
| 
      
 41 
     | 
    
         
            +
                  process_execution_with_timeout unless die?
         
     | 
| 
       28 
42 
     | 
    
         
             
                end
         
     | 
| 
       29 
43 
     | 
    
         | 
| 
       30 
44 
     | 
    
         
             
                # Process the execution, handling timeouts and exceptions.
         
     | 
| 
       31 
     | 
    
         
            -
                def process_execution_with_timeout 
     | 
| 
      
 45 
     | 
    
         
            +
                def process_execution_with_timeout
         
     | 
| 
       32 
46 
     | 
    
         
             
                  execution.start!
         
     | 
| 
       33 
47 
     | 
    
         
             
                  begin
         
     | 
| 
       34 
48 
     | 
    
         
             
                    if timeout
         
     | 
| 
       35 
     | 
    
         
            -
                      timeout_method.call(timeout){ process_execution 
     | 
| 
      
 49 
     | 
    
         
            +
                      timeout_method.call(timeout){ process_execution }
         
     | 
| 
       36 
50 
     | 
    
         
             
                    else
         
     | 
| 
       37 
     | 
    
         
            -
                      process_execution 
     | 
| 
      
 51 
     | 
    
         
            +
                      process_execution
         
     | 
| 
       38 
52 
     | 
    
         
             
                    end
         
     | 
| 
       39 
53 
     | 
    
         
             
                  rescue Timeout::Error => e
         
     | 
| 
       40 
54 
     | 
    
         
             
                    execution.timed_out!
         
     | 
| 
         @@ -46,18 +60,17 @@ class ThreadStorm 
     | 
|
| 
       46 
60 
     | 
    
         
             
                end
         
     | 
| 
       47 
61 
     | 
    
         | 
| 
       48 
62 
     | 
    
         
             
                # Seriously, process the execution.
         
     | 
| 
       49 
     | 
    
         
            -
                def process_execution 
     | 
| 
      
 63 
     | 
    
         
            +
                def process_execution
         
     | 
| 
       50 
64 
     | 
    
         
             
                  execution.value = execution.block.call(*execution.args)
         
     | 
| 
       51 
65 
     | 
    
         
             
                end
         
     | 
| 
       52 
66 
     | 
    
         | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
                  @die = true
         
     | 
| 
      
 67 
     | 
    
         
            +
                def busy?
         
     | 
| 
      
 68 
     | 
    
         
            +
                  !!@execution and not die?
         
     | 
| 
       56 
69 
     | 
    
         
             
                end
         
     | 
| 
       57 
70 
     | 
    
         | 
| 
       58 
71 
     | 
    
         
             
                # True if this worker's thread should die.
         
     | 
| 
       59 
72 
     | 
    
         
             
                def die?
         
     | 
| 
       60 
     | 
    
         
            -
                   
     | 
| 
      
 73 
     | 
    
         
            +
                  @execution == :die
         
     | 
| 
       61 
74 
     | 
    
         
             
                end
         
     | 
| 
       62 
75 
     | 
    
         | 
| 
       63 
76 
     | 
    
         
             
              end
         
     | 
    
        data/lib/thread_storm.rb
    CHANGED
    
    | 
         @@ -1,13 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "thread"
         
     | 
| 
       2 
2 
     | 
    
         
             
            require "timeout"
         
     | 
| 
       3 
3 
     | 
    
         
             
            require "thread_storm/active_support"
         
     | 
| 
       4 
     | 
    
         
            -
            require "thread_storm/ 
     | 
| 
      
 4 
     | 
    
         
            +
            require "thread_storm/sentinel"
         
     | 
| 
       5 
5 
     | 
    
         
             
            require "thread_storm/execution"
         
     | 
| 
       6 
6 
     | 
    
         
             
            require "thread_storm/worker"
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
            class ThreadStorm
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
              # Array of executions in order as defined by calls to ThreadStorm#execute.
         
     | 
| 
      
 10 
     | 
    
         
            +
              # Array of executions in order as they are defined by calls to ThreadStorm#execute.
         
     | 
| 
       11 
11 
     | 
    
         
             
              attr_reader :executions
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
              # Valid options are
         
     | 
| 
         @@ -16,16 +16,18 @@ class ThreadStorm 
     | 
|
| 
       16 
16 
     | 
    
         
             
              #   :timeout_method => An object that implements something like Timeout.timeout via #call.  Default is Timeout.method(:timeout).
         
     | 
| 
       17 
17 
     | 
    
         
             
              #   :default_value => Value of an execution if it times out or errors.  Default is nil.
         
     | 
| 
       18 
18 
     | 
    
         
             
              #   :reraise => True if you want exceptions reraised when ThreadStorm#join is called.  Default is true.
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   :execute_blocks => True if you want #execute to block until there is an available thread.  Default is false.
         
     | 
| 
       19 
20 
     | 
    
         
             
              def initialize(options = {})
         
     | 
| 
       20 
21 
     | 
    
         
             
                @options = options.option_merge :size => 2,
         
     | 
| 
       21 
22 
     | 
    
         
             
                                                :timeout => nil,
         
     | 
| 
       22 
23 
     | 
    
         
             
                                                :timeout_method => Timeout.method(:timeout),
         
     | 
| 
       23 
24 
     | 
    
         
             
                                                :default_value => nil,
         
     | 
| 
       24 
     | 
    
         
            -
                                                :reraise => true
         
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
      
 25 
     | 
    
         
            +
                                                :reraise => true,
         
     | 
| 
      
 26 
     | 
    
         
            +
                                                :execute_blocks => false
         
     | 
| 
      
 27 
     | 
    
         
            +
                @sentinel = Sentinel.new
         
     | 
| 
      
 28 
     | 
    
         
            +
                @queue = []
         
     | 
| 
       26 
29 
     | 
    
         
             
                @executions = []
         
     | 
| 
       27 
     | 
    
         
            -
                @workers = (1..@options[:size]).collect{ Worker.new(@queue, @options) }
         
     | 
| 
       28 
     | 
    
         
            -
                @start_time = Time.now
         
     | 
| 
      
 30 
     | 
    
         
            +
                @workers = (1..@options[:size]).collect{ Worker.new(@queue, @sentinel, @options) }
         
     | 
| 
       29 
31 
     | 
    
         
             
                if block_given?
         
     | 
| 
       30 
32 
     | 
    
         
             
                  yield(self)
         
     | 
| 
       31 
33 
     | 
    
         
             
                  join
         
     | 
| 
         @@ -33,25 +35,20 @@ class ThreadStorm 
     | 
|
| 
       33 
35 
     | 
    
         
             
                end
         
     | 
| 
       34 
36 
     | 
    
         
             
              end
         
     | 
| 
       35 
37 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
              def size
         
     | 
| 
      
 38 
     | 
    
         
            +
              def size #:nodoc:
         
     | 
| 
       37 
39 
     | 
    
         
             
                @options[:size]
         
     | 
| 
       38 
40 
     | 
    
         
             
              end
         
     | 
| 
       39 
41 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
               
     | 
| 
       41 
     | 
    
         
            -
                @options[:default_value]
         
     | 
| 
       42 
     | 
    
         
            -
              end
         
     | 
| 
       43 
     | 
    
         
            -
              
         
     | 
| 
       44 
     | 
    
         
            -
              def reraise?
         
     | 
| 
       45 
     | 
    
         
            -
                @options[:reraise]
         
     | 
| 
       46 
     | 
    
         
            -
              end
         
     | 
| 
       47 
     | 
    
         
            -
              
         
     | 
| 
       48 
     | 
    
         
            -
              # Create and execution and schedules it to be run by the thread pool.
         
     | 
| 
      
 42 
     | 
    
         
            +
              # Creates an execution and schedules it to be run by the thread pool.
         
     | 
| 
       49 
43 
     | 
    
         
             
              # Return value is a ThreadStorm::Execution.
         
     | 
| 
       50 
44 
     | 
    
         
             
              def execute(*args, &block)
         
     | 
| 
       51 
     | 
    
         
            -
                Execution.new(args, &block).tap do |execution|
         
     | 
| 
       52 
     | 
    
         
            -
                   
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
      
 45 
     | 
    
         
            +
                Execution.new(args, default_value, &block).tap do |execution|
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @sentinel.synchronize do |e_cond, p_cond|
         
     | 
| 
      
 47 
     | 
    
         
            +
                    e_cond.wait_while{ all_workers_busy? } if execute_blocks?
         
     | 
| 
      
 48 
     | 
    
         
            +
                    @queue << execution
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @executions << execution
         
     | 
| 
      
 50 
     | 
    
         
            +
                    p_cond.signal
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
       55 
52 
     | 
    
         
             
                end
         
     | 
| 
       56 
53 
     | 
    
         
             
              end
         
     | 
| 
       57 
54 
     | 
    
         | 
| 
         @@ -66,22 +63,65 @@ class ThreadStorm 
     | 
|
| 
       66 
63 
     | 
    
         | 
| 
       67 
64 
     | 
    
         
             
              # Calls ThreadStorm#join, then collects the values of each execution.
         
     | 
| 
       68 
65 
     | 
    
         
             
              def values
         
     | 
| 
       69 
     | 
    
         
            -
                join
         
     | 
| 
       70 
     | 
    
         
            -
                @executions.collect{ |execution| execution.value }
         
     | 
| 
      
 66 
     | 
    
         
            +
                join and @executions.collect{ |execution| execution.value }
         
     | 
| 
       71 
67 
     | 
    
         
             
              end
         
     | 
| 
       72 
68 
     | 
    
         | 
| 
       73 
69 
     | 
    
         
             
              # Signals the worker threads to terminate immediately (ignoring any pending
         
     | 
| 
       74 
70 
     | 
    
         
             
              # executions) and blocks until they do.
         
     | 
| 
       75 
71 
     | 
    
         
             
              def shutdown
         
     | 
| 
       76 
     | 
    
         
            -
                @ 
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
      
 72 
     | 
    
         
            +
                @sentinel.synchronize do |e_cond, p_cond|
         
     | 
| 
      
 73 
     | 
    
         
            +
                  @queue.replace([:die] * size)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  p_cond.broadcast
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
       78 
76 
     | 
    
         
             
                @workers.each{ |worker| worker.thread.join }
         
     | 
| 
       79 
77 
     | 
    
         
             
                true
         
     | 
| 
       80 
78 
     | 
    
         
             
              end
         
     | 
| 
       81 
79 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
              # Returns  
     | 
| 
      
 80 
     | 
    
         
            +
              # Returns workers that are currently running executions.
         
     | 
| 
      
 81 
     | 
    
         
            +
              def busy_workers
         
     | 
| 
      
 82 
     | 
    
         
            +
                @workers.select{ |worker| worker.busy? }
         
     | 
| 
      
 83 
     | 
    
         
            +
              end
         
     | 
| 
      
 84 
     | 
    
         
            +
              
         
     | 
| 
      
 85 
     | 
    
         
            +
              # Returns an array of Ruby threads in the pool.
         
     | 
| 
       83 
86 
     | 
    
         
             
              def threads
         
     | 
| 
       84 
87 
     | 
    
         
             
                @workers.collect{ |worker| worker.thread }
         
     | 
| 
       85 
88 
     | 
    
         
             
              end
         
     | 
| 
       86 
89 
     | 
    
         | 
| 
      
 90 
     | 
    
         
            +
              # Removes executions stored at ThreadStorm#executions.  You can selectively remove
         
     | 
| 
      
 91 
     | 
    
         
            +
              # them by passing in a block or a symbol.  The following two lines are equivalent.
         
     | 
| 
      
 92 
     | 
    
         
            +
              #   storm.clear_executions(:finished?)
         
     | 
| 
      
 93 
     | 
    
         
            +
              #   storm.clear_executions{ |e| e.finished? }
         
     | 
| 
      
 94 
     | 
    
         
            +
              # Because of the nature of threading, the following code could happen:
         
     | 
| 
      
 95 
     | 
    
         
            +
              #   storm.clear_executions(:finished?)
         
     | 
| 
      
 96 
     | 
    
         
            +
              #   storm.executions.any?{ |e| e.finished? }
         
     | 
| 
      
 97 
     | 
    
         
            +
              # Some executions could have finished between the two calls.
         
     | 
| 
      
 98 
     | 
    
         
            +
              def clear_executions(method_name = nil, &block)
         
     | 
| 
      
 99 
     | 
    
         
            +
                cleared, @executions = @executions.separate do |execution|
         
     | 
| 
      
 100 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 101 
     | 
    
         
            +
                    yield(execution)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  else
         
     | 
| 
      
 103 
     | 
    
         
            +
                    execution.send(method_name)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
                cleared
         
     | 
| 
      
 107 
     | 
    
         
            +
              end
         
     | 
| 
      
 108 
     | 
    
         
            +
              
         
     | 
| 
      
 109 
     | 
    
         
            +
            private
         
     | 
| 
      
 110 
     | 
    
         
            +
              
         
     | 
| 
      
 111 
     | 
    
         
            +
              def default_value #:nodoc:
         
     | 
| 
      
 112 
     | 
    
         
            +
                @options[:default_value]
         
     | 
| 
      
 113 
     | 
    
         
            +
              end
         
     | 
| 
      
 114 
     | 
    
         
            +
              
         
     | 
| 
      
 115 
     | 
    
         
            +
              def reraise? #:nodoc:
         
     | 
| 
      
 116 
     | 
    
         
            +
                @options[:reraise]
         
     | 
| 
      
 117 
     | 
    
         
            +
              end
         
     | 
| 
      
 118 
     | 
    
         
            +
              
         
     | 
| 
      
 119 
     | 
    
         
            +
              def execute_blocks? #:nodoc:
         
     | 
| 
      
 120 
     | 
    
         
            +
                @options[:execute_blocks]
         
     | 
| 
      
 121 
     | 
    
         
            +
              end
         
     | 
| 
      
 122 
     | 
    
         
            +
              
         
     | 
| 
      
 123 
     | 
    
         
            +
              def all_workers_busy? #:nodoc:
         
     | 
| 
      
 124 
     | 
    
         
            +
                @workers.all?{ |worker| worker.busy? }
         
     | 
| 
      
 125 
     | 
    
         
            +
              end
         
     | 
| 
      
 126 
     | 
    
         
            +
              
         
     | 
| 
       87 
127 
     | 
    
         
             
            end
         
     | 
    
        data/test/test_thread_storm.rb
    CHANGED
    
    | 
         @@ -3,40 +3,40 @@ require 'helper' 
     | 
|
| 
       3 
3 
     | 
    
         
             
            class TestThreadStorm < Test::Unit::TestCase
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
              def test_no_concurrency
         
     | 
| 
       6 
     | 
    
         
            -
                 
     | 
| 
       7 
     | 
    
         
            -
                 
     | 
| 
       8 
     | 
    
         
            -
                 
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
       10 
     | 
    
         
            -
                assert_equal %w[one two three],  
     | 
| 
       11 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 6 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 1
         
     | 
| 
      
 7 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 8 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "two" }
         
     | 
| 
      
 9 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 10 
     | 
    
         
            +
                assert_equal %w[one two three], storm.values
         
     | 
| 
      
 11 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
       12 
12 
     | 
    
         
             
              end
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
              def test_partial_concurrency
         
     | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
       16 
     | 
    
         
            -
                 
     | 
| 
       17 
     | 
    
         
            -
                 
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
       19 
     | 
    
         
            -
                assert_equal %w[one two three],  
     | 
| 
       20 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 15 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 2
         
     | 
| 
      
 16 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 17 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "two" }
         
     | 
| 
      
 18 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 19 
     | 
    
         
            +
                assert_equal %w[one two three], storm.values
         
     | 
| 
      
 20 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
       21 
21 
     | 
    
         
             
              end
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
              def test_full_concurrency
         
     | 
| 
       24 
     | 
    
         
            -
                 
     | 
| 
       25 
     | 
    
         
            -
                 
     | 
| 
       26 
     | 
    
         
            -
                 
     | 
| 
       27 
     | 
    
         
            -
                 
     | 
| 
       28 
     | 
    
         
            -
                assert_equal %w[one two three],  
     | 
| 
       29 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 24 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 3
         
     | 
| 
      
 25 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 26 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "two" }
         
     | 
| 
      
 27 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 28 
     | 
    
         
            +
                assert_equal %w[one two three], storm.values
         
     | 
| 
      
 29 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
       30 
30 
     | 
    
         
             
              end
         
     | 
| 
       31 
31 
     | 
    
         | 
| 
       32 
32 
     | 
    
         
             
              def test_timeout_no_concurrency
         
     | 
| 
       33 
     | 
    
         
            -
                 
     | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
       35 
     | 
    
         
            -
                 
     | 
| 
       36 
     | 
    
         
            -
                 
     | 
| 
       37 
     | 
    
         
            -
                assert_equal ["one", nil, "three"],  
     | 
| 
       38 
     | 
    
         
            -
                assert  
     | 
| 
       39 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 33 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 1, :timeout => 0.015
         
     | 
| 
      
 34 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 35 
     | 
    
         
            +
                storm.execute{ sleep(0.02); "two" }
         
     | 
| 
      
 36 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 37 
     | 
    
         
            +
                assert_equal ["one", nil, "three"], storm.values
         
     | 
| 
      
 38 
     | 
    
         
            +
                assert storm.executions[1].timed_out?
         
     | 
| 
      
 39 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
       40 
40 
     | 
    
         
             
              end
         
     | 
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
              # Tricky...
         
     | 
| 
         @@ -44,73 +44,119 @@ class TestThreadStorm < Test::Unit::TestCase 
     | 
|
| 
       44 
44 
     | 
    
         
             
              # 2 0.015s ------
         
     | 
| 
       45 
45 
     | 
    
         
             
              # 3 0.01s      ----
         
     | 
| 
       46 
46 
     | 
    
         
             
              def test_timeout_partial_concurrency
         
     | 
| 
       47 
     | 
    
         
            -
                 
     | 
| 
       48 
     | 
    
         
            -
                 
     | 
| 
       49 
     | 
    
         
            -
                 
     | 
| 
       50 
     | 
    
         
            -
                 
     | 
| 
       51 
     | 
    
         
            -
                assert_equal ["one", nil, "three"],  
     | 
| 
       52 
     | 
    
         
            -
                assert  
     | 
| 
       53 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 47 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 2, :timeout => 0.015
         
     | 
| 
      
 48 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 49 
     | 
    
         
            +
                storm.execute{ sleep(0.02); "two" }
         
     | 
| 
      
 50 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 51 
     | 
    
         
            +
                assert_equal ["one", nil, "three"], storm.values
         
     | 
| 
      
 52 
     | 
    
         
            +
                assert storm.executions[1].timed_out?
         
     | 
| 
      
 53 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
       54 
54 
     | 
    
         
             
              end
         
     | 
| 
       55 
55 
     | 
    
         | 
| 
       56 
56 
     | 
    
         
             
              def test_timeout_full_concurrency
         
     | 
| 
       57 
     | 
    
         
            -
                 
     | 
| 
       58 
     | 
    
         
            -
                 
     | 
| 
       59 
     | 
    
         
            -
                 
     | 
| 
       60 
     | 
    
         
            -
                 
     | 
| 
       61 
     | 
    
         
            -
                assert_equal ["one", nil, "three"],  
     | 
| 
       62 
     | 
    
         
            -
                assert  
     | 
| 
       63 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 57 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 3, :timeout => 0.015
         
     | 
| 
      
 58 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 59 
     | 
    
         
            +
                storm.execute{ sleep(0.02); "two" }
         
     | 
| 
      
 60 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 61 
     | 
    
         
            +
                assert_equal ["one", nil, "three"], storm.values
         
     | 
| 
      
 62 
     | 
    
         
            +
                assert storm.executions[1].timed_out?
         
     | 
| 
      
 63 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
       64 
64 
     | 
    
         
             
              end
         
     | 
| 
       65 
65 
     | 
    
         | 
| 
       66 
66 
     | 
    
         
             
              def test_timeout_with_default_value
         
     | 
| 
       67 
     | 
    
         
            -
                 
     | 
| 
       68 
     | 
    
         
            -
                 
     | 
| 
       69 
     | 
    
         
            -
                 
     | 
| 
       70 
     | 
    
         
            -
                 
     | 
| 
       71 
     | 
    
         
            -
                assert_equal ["one", "timed out", "three"],  
     | 
| 
       72 
     | 
    
         
            -
                assert  
     | 
| 
       73 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 67 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 1, :timeout => 0.015, :default_value => "timed out"
         
     | 
| 
      
 68 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 69 
     | 
    
         
            +
                storm.execute{ sleep(0.02); "two" }
         
     | 
| 
      
 70 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 71 
     | 
    
         
            +
                assert_equal ["one", "timed out", "three"], storm.values
         
     | 
| 
      
 72 
     | 
    
         
            +
                assert storm.executions[1].timed_out?
         
     | 
| 
      
 73 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
       74 
74 
     | 
    
         
             
              end
         
     | 
| 
       75 
75 
     | 
    
         | 
| 
       76 
76 
     | 
    
         
             
              def test_shutdown
         
     | 
| 
       77 
77 
     | 
    
         
             
                original_thread_count = Thread.list.length
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
                 
     | 
| 
       80 
     | 
    
         
            -
                 
     | 
| 
       81 
     | 
    
         
            -
                 
     | 
| 
       82 
     | 
    
         
            -
                 
     | 
| 
       83 
     | 
    
         
            -
                 
     | 
| 
      
 79 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 3
         
     | 
| 
      
 80 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 81 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "two" }
         
     | 
| 
      
 82 
     | 
    
         
            +
                storm.execute{ sleep(0.01); "three" }
         
     | 
| 
      
 83 
     | 
    
         
            +
                storm.join
         
     | 
| 
       84 
84 
     | 
    
         | 
| 
       85 
85 
     | 
    
         
             
                assert_equal original_thread_count + 3, Thread.list.length
         
     | 
| 
       86 
     | 
    
         
            -
                 
     | 
| 
      
 86 
     | 
    
         
            +
                storm.shutdown
         
     | 
| 
       87 
87 
     | 
    
         
             
                assert_equal original_thread_count, Thread.list.length
         
     | 
| 
       88 
88 
     | 
    
         
             
              end
         
     | 
| 
       89 
89 
     | 
    
         | 
| 
       90 
90 
     | 
    
         
             
              def test_shutdown_before_pop
         
     | 
| 
       91 
     | 
    
         
            -
                 
     | 
| 
       92 
     | 
    
         
            -
                 
     | 
| 
      
 91 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 3
         
     | 
| 
      
 92 
     | 
    
         
            +
                storm.shutdown
         
     | 
| 
       93 
93 
     | 
    
         
             
              end
         
     | 
| 
       94 
94 
     | 
    
         | 
| 
       95 
95 
     | 
    
         
             
              def test_args
         
     | 
| 
       96 
     | 
    
         
            -
                 
     | 
| 
      
 96 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 2
         
     | 
| 
       97 
97 
     | 
    
         
             
                %w[one two three four five].each do |word|
         
     | 
| 
       98 
     | 
    
         
            -
                   
     | 
| 
      
 98 
     | 
    
         
            +
                  storm.execute(word){ |w| sleep(0.01); w }
         
     | 
| 
       99 
99 
     | 
    
         
             
                end
         
     | 
| 
       100 
     | 
    
         
            -
                 
     | 
| 
       101 
     | 
    
         
            -
                assert_equal %w[one two three four five],  
     | 
| 
      
 100 
     | 
    
         
            +
                storm.join
         
     | 
| 
      
 101 
     | 
    
         
            +
                assert_equal %w[one two three four five], storm.values
         
     | 
| 
       102 
102 
     | 
    
         
             
              end
         
     | 
| 
       103 
103 
     | 
    
         | 
| 
       104 
104 
     | 
    
         
             
              def test_new_with_block
         
     | 
| 
       105 
105 
     | 
    
         
             
                thread_count = Thread.list.length
         
     | 
| 
       106 
     | 
    
         
            -
                 
     | 
| 
       107 
     | 
    
         
            -
                   
     | 
| 
       108 
     | 
    
         
            -
                   
     | 
| 
       109 
     | 
    
         
            -
                   
     | 
| 
      
 106 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 1 do |storm|
         
     | 
| 
      
 107 
     | 
    
         
            +
                  storm.execute{ sleep(0.01); "one" }
         
     | 
| 
      
 108 
     | 
    
         
            +
                  storm.execute{ sleep(0.01); "two" }
         
     | 
| 
      
 109 
     | 
    
         
            +
                  storm.execute{ sleep(0.01); "three" }
         
     | 
| 
       110 
110 
     | 
    
         
             
                end
         
     | 
| 
       111 
111 
     | 
    
         
             
                assert_equal thread_count, Thread.list.length
         
     | 
| 
       112 
     | 
    
         
            -
                assert_equal %w[one two three],  
     | 
| 
       113 
     | 
    
         
            -
                assert_all_threads_worked( 
     | 
| 
      
 112 
     | 
    
         
            +
                assert_equal %w[one two three], storm.values
         
     | 
| 
      
 113 
     | 
    
         
            +
                assert_all_threads_worked(storm)
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
              
         
     | 
| 
      
 116 
     | 
    
         
            +
              def test_execute_blocks
         
     | 
| 
      
 117 
     | 
    
         
            +
                t1 = Thread.new do
         
     | 
| 
      
 118 
     | 
    
         
            +
                  storm = ThreadStorm.new :size => 1, :execute_blocks => true
         
     | 
| 
      
 119 
     | 
    
         
            +
                  storm.execute{ sleep }
         
     | 
| 
      
 120 
     | 
    
         
            +
                  storm.execute{ nil }
         
     | 
| 
      
 121 
     | 
    
         
            +
                end
         
     | 
| 
      
 122 
     | 
    
         
            +
                t2 = Thread.new do
         
     | 
| 
      
 123 
     | 
    
         
            +
                  storm = ThreadStorm.new :size => 1, :execute_blocks => false
         
     | 
| 
      
 124 
     | 
    
         
            +
                  storm.execute{ sleep }
         
     | 
| 
      
 125 
     | 
    
         
            +
                  storm.execute{ nil }
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
                sleep(0.1) # How else??
         
     | 
| 
      
 128 
     | 
    
         
            +
                assert_equal "sleep", t1.status
         
     | 
| 
      
 129 
     | 
    
         
            +
                assert_equal false, t2.status
         
     | 
| 
      
 130 
     | 
    
         
            +
              end
         
     | 
| 
      
 131 
     | 
    
         
            +
              
         
     | 
| 
      
 132 
     | 
    
         
            +
              def test_clear_executions
         
     | 
| 
      
 133 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 3
         
     | 
| 
      
 134 
     | 
    
         
            +
                storm.execute{ sleep }
         
     | 
| 
      
 135 
     | 
    
         
            +
                storm.execute{ sleep(0.1) }
         
     | 
| 
      
 136 
     | 
    
         
            +
                storm.execute{ sleep(0.1) }
         
     | 
| 
      
 137 
     | 
    
         
            +
                sleep(0.2) # Ugh another test based on sleeping.
         
     | 
| 
      
 138 
     | 
    
         
            +
                finished = storm.clear_executions(:finished?)
         
     | 
| 
      
 139 
     | 
    
         
            +
                assert_equal 2, finished.length
         
     | 
| 
      
 140 
     | 
    
         
            +
                assert_equal 1, storm.executions.length
         
     | 
| 
      
 141 
     | 
    
         
            +
              end
         
     | 
| 
      
 142 
     | 
    
         
            +
              
         
     | 
| 
      
 143 
     | 
    
         
            +
              def test_execution_blocks_again
         
     | 
| 
      
 144 
     | 
    
         
            +
                storm = ThreadStorm.new :size => 10, :execute_blocks => true
         
     | 
| 
      
 145 
     | 
    
         
            +
                20.times{ storm.execute{ sleep(rand) } }
         
     | 
| 
      
 146 
     | 
    
         
            +
                storm.join
         
     | 
| 
      
 147 
     | 
    
         
            +
                storm.shutdown
         
     | 
| 
      
 148 
     | 
    
         
            +
              end
         
     | 
| 
      
 149 
     | 
    
         
            +
              
         
     | 
| 
      
 150 
     | 
    
         
            +
              def test_for_deadlocks
         
     | 
| 
      
 151 
     | 
    
         
            +
                ThreadStorm.new :size => 10, :execute_blocks => true do |storm|
         
     | 
| 
      
 152 
     | 
    
         
            +
                  20.times do
         
     | 
| 
      
 153 
     | 
    
         
            +
                    storm.execute do
         
     | 
| 
      
 154 
     | 
    
         
            +
                      ThreadStorm.new :size => 10, :timeout => 0.5 do |storm2|
         
     | 
| 
      
 155 
     | 
    
         
            +
                        20.times{ storm2.execute{ sleep(rand) } }
         
     | 
| 
      
 156 
     | 
    
         
            +
                      end
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
                end
         
     | 
| 
       114 
160 
     | 
    
         
             
              end
         
     | 
| 
       115 
161 
     | 
    
         | 
| 
       116 
162 
     | 
    
         
             
            end
         
     | 
    
        data/thread_storm.gemspec
    CHANGED
    
    | 
         @@ -5,11 +5,11 @@ 
     | 
|
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            Gem::Specification.new do |s|
         
     | 
| 
       7 
7 
     | 
    
         
             
              s.name = %q{thread_storm}
         
     | 
| 
       8 
     | 
    
         
            -
              s.version = "0. 
     | 
| 
      
 8 
     | 
    
         
            +
              s.version = "0.5.0"
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         
     | 
| 
       11 
11 
     | 
    
         
             
              s.authors = ["Christopher J. Bottaro"]
         
     | 
| 
       12 
     | 
    
         
            -
              s.date = %q{2010-06- 
     | 
| 
      
 12 
     | 
    
         
            +
              s.date = %q{2010-06-21}
         
     | 
| 
       13 
13 
     | 
    
         
             
              s.description = %q{Simple thread pool with timeouts, default values, error handling, state tracking and unit tests.}
         
     | 
| 
       14 
14 
     | 
    
         
             
              s.email = %q{cjbottaro@alumni.cs.utexas.edu}
         
     | 
| 
       15 
15 
     | 
    
         
             
              s.extra_rdoc_files = [
         
     | 
| 
         @@ -27,7 +27,7 @@ Gem::Specification.new do |s| 
     | 
|
| 
       27 
27 
     | 
    
         
             
                 "lib/thread_storm.rb",
         
     | 
| 
       28 
28 
     | 
    
         
             
                 "lib/thread_storm/active_support.rb",
         
     | 
| 
       29 
29 
     | 
    
         
             
                 "lib/thread_storm/execution.rb",
         
     | 
| 
       30 
     | 
    
         
            -
                 "lib/thread_storm/ 
     | 
| 
      
 30 
     | 
    
         
            +
                 "lib/thread_storm/sentinel.rb",
         
     | 
| 
       31 
31 
     | 
    
         
             
                 "lib/thread_storm/worker.rb",
         
     | 
| 
       32 
32 
     | 
    
         
             
                 "test/helper.rb",
         
     | 
| 
       33 
33 
     | 
    
         
             
                 "test/test_thread_storm.rb",
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,13 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification 
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: thread_storm
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version 
         
     | 
| 
       4 
     | 
    
         
            -
              hash:  
     | 
| 
      
 4 
     | 
    
         
            +
              hash: 11
         
     | 
| 
       5 
5 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       6 
6 
     | 
    
         
             
              segments: 
         
     | 
| 
       7 
7 
     | 
    
         
             
              - 0
         
     | 
| 
       8 
     | 
    
         
            -
              -  
     | 
| 
      
 8 
     | 
    
         
            +
              - 5
         
     | 
| 
       9 
9 
     | 
    
         
             
              - 0
         
     | 
| 
       10 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 10 
     | 
    
         
            +
              version: 0.5.0
         
     | 
| 
       11 
11 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       12 
12 
     | 
    
         
             
            authors: 
         
     | 
| 
       13 
13 
     | 
    
         
             
            - Christopher J. Bottaro
         
     | 
| 
         @@ -15,7 +15,7 @@ autorequire: 
     | 
|
| 
       15 
15 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       16 
16 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
            date: 2010-06- 
     | 
| 
      
 18 
     | 
    
         
            +
            date: 2010-06-21 00:00:00 -05:00
         
     | 
| 
       19 
19 
     | 
    
         
             
            default_executable: 
         
     | 
| 
       20 
20 
     | 
    
         
             
            dependencies: []
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
         @@ -39,7 +39,7 @@ files: 
     | 
|
| 
       39 
39 
     | 
    
         
             
            - lib/thread_storm.rb
         
     | 
| 
       40 
40 
     | 
    
         
             
            - lib/thread_storm/active_support.rb
         
     | 
| 
       41 
41 
     | 
    
         
             
            - lib/thread_storm/execution.rb
         
     | 
| 
       42 
     | 
    
         
            -
            - lib/thread_storm/ 
     | 
| 
      
 42 
     | 
    
         
            +
            - lib/thread_storm/sentinel.rb
         
     | 
| 
       43 
43 
     | 
    
         
             
            - lib/thread_storm/worker.rb
         
     | 
| 
       44 
44 
     | 
    
         
             
            - test/helper.rb
         
     | 
| 
       45 
45 
     | 
    
         
             
            - test/test_thread_storm.rb
         
     | 
    
        data/lib/thread_storm/queue.rb
    DELETED
    
    | 
         @@ -1,45 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require "thread"
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            class ThreadStorm
         
     | 
| 
       4 
     | 
    
         
            -
              class Queue #:nodoc:
         
     | 
| 
       5 
     | 
    
         
            -
                
         
     | 
| 
       6 
     | 
    
         
            -
                def initialize
         
     | 
| 
       7 
     | 
    
         
            -
                  @lock  = Mutex.new
         
     | 
| 
       8 
     | 
    
         
            -
                  @cond  = ConditionVariable.new
         
     | 
| 
       9 
     | 
    
         
            -
                  @die   = false
         
     | 
| 
       10 
     | 
    
         
            -
                  @queue = []
         
     | 
| 
       11 
     | 
    
         
            -
                end
         
     | 
| 
       12 
     | 
    
         
            -
                
         
     | 
| 
       13 
     | 
    
         
            -
                # Pushes a value on the queue and wakes up the next thread waiting on #pop.
         
     | 
| 
       14 
     | 
    
         
            -
                def push(value)
         
     | 
| 
       15 
     | 
    
         
            -
                  @lock.synchronize do 
         
     | 
| 
       16 
     | 
    
         
            -
                    @queue.push(value)
         
     | 
| 
       17 
     | 
    
         
            -
                    @cond.signal # Wake up next thread waiting on #pop.
         
     | 
| 
       18 
     | 
    
         
            -
                  end
         
     | 
| 
       19 
     | 
    
         
            -
                end
         
     | 
| 
       20 
     | 
    
         
            -
                
         
     | 
| 
       21 
     | 
    
         
            -
                # Pops a value of the queue.  Blocks if the queue is empty.
         
     | 
| 
       22 
     | 
    
         
            -
                def pop
         
     | 
| 
       23 
     | 
    
         
            -
                  @lock.synchronize do
         
     | 
| 
       24 
     | 
    
         
            -
                    @cond.wait(@lock) if @queue.empty? and not die?
         
     | 
| 
       25 
     | 
    
         
            -
                    @queue.pop
         
     | 
| 
       26 
     | 
    
         
            -
                  end
         
     | 
| 
       27 
     | 
    
         
            -
                end
         
     | 
| 
       28 
     | 
    
         
            -
                
         
     | 
| 
       29 
     | 
    
         
            -
                # Clears the queue.  Any calls to #pop will immediately return with nil.
         
     | 
| 
       30 
     | 
    
         
            -
                def die!
         
     | 
| 
       31 
     | 
    
         
            -
                  @lock.synchronize do
         
     | 
| 
       32 
     | 
    
         
            -
                    @die = true
         
     | 
| 
       33 
     | 
    
         
            -
                    @queue.clear
         
     | 
| 
       34 
     | 
    
         
            -
                    @cond.broadcast # Wake up any threads waiting on #pop.
         
     | 
| 
       35 
     | 
    
         
            -
                  end
         
     | 
| 
       36 
     | 
    
         
            -
                end
         
     | 
| 
       37 
     | 
    
         
            -
              
         
     | 
| 
       38 
     | 
    
         
            -
              private
         
     | 
| 
       39 
     | 
    
         
            -
              
         
     | 
| 
       40 
     | 
    
         
            -
                def die?
         
     | 
| 
       41 
     | 
    
         
            -
                  !!@die
         
     | 
| 
       42 
     | 
    
         
            -
                end
         
     | 
| 
       43 
     | 
    
         
            -
                
         
     | 
| 
       44 
     | 
    
         
            -
              end
         
     | 
| 
       45 
     | 
    
         
            -
            end
         
     |