concurrent-ruby 0.2.1 → 0.2.2
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -21
- data/README.md +276 -275
- data/lib/concurrent.rb +28 -28
- data/lib/concurrent/agent.rb +114 -114
- data/lib/concurrent/cached_thread_pool.rb +131 -131
- data/lib/concurrent/defer.rb +65 -65
- data/lib/concurrent/event.rb +60 -60
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
- data/lib/concurrent/executor.rb +96 -96
- data/lib/concurrent/fixed_thread_pool.rb +99 -99
- data/lib/concurrent/functions.rb +120 -120
- data/lib/concurrent/future.rb +42 -42
- data/lib/concurrent/global_thread_pool.rb +24 -16
- data/lib/concurrent/goroutine.rb +29 -29
- data/lib/concurrent/null_thread_pool.rb +22 -22
- data/lib/concurrent/obligation.rb +67 -67
- data/lib/concurrent/promise.rb +174 -174
- data/lib/concurrent/reactor.rb +166 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
- data/lib/concurrent/supervisor.rb +105 -105
- data/lib/concurrent/thread_pool.rb +76 -76
- data/lib/concurrent/utilities.rb +32 -32
- data/lib/concurrent/version.rb +3 -3
- data/lib/concurrent_ruby.rb +1 -1
- data/md/agent.md +123 -123
- data/md/defer.md +174 -174
- data/md/event.md +32 -32
- data/md/executor.md +187 -187
- data/md/future.md +83 -83
- data/md/goroutine.md +52 -52
- data/md/obligation.md +32 -32
- data/md/promise.md +227 -227
- data/md/thread_pool.md +224 -224
- data/spec/concurrent/agent_spec.rb +390 -386
- data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
- data/spec/concurrent/defer_spec.rb +199 -195
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
- data/spec/concurrent/event_spec.rb +134 -134
- data/spec/concurrent/executor_spec.rb +200 -200
- data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
- data/spec/concurrent/functions_spec.rb +217 -217
- data/spec/concurrent/future_spec.rb +112 -108
- data/spec/concurrent/global_thread_pool_spec.rb +11 -38
- data/spec/concurrent/goroutine_spec.rb +67 -67
- data/spec/concurrent/null_thread_pool_spec.rb +57 -57
- data/spec/concurrent/obligation_shared.rb +132 -132
- data/spec/concurrent/promise_spec.rb +316 -312
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
- data/spec/concurrent/reactor_spec.rb +364 -364
- data/spec/concurrent/supervisor_spec.rb +269 -269
- data/spec/concurrent/thread_pool_shared.rb +204 -204
- data/spec/concurrent/uses_global_thread_pool_shared.rb +64 -0
- data/spec/concurrent/utilities_spec.rb +74 -74
- data/spec/spec_helper.rb +32 -32
- metadata +17 -19
data/lib/concurrent.rb
CHANGED
@@ -1,28 +1,28 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/version'
|
4
|
-
|
5
|
-
require 'concurrent/event'
|
6
|
-
|
7
|
-
require 'concurrent/agent'
|
8
|
-
require 'concurrent/defer'
|
9
|
-
require 'concurrent/executor'
|
10
|
-
require 'concurrent/future'
|
11
|
-
require 'concurrent/goroutine'
|
12
|
-
require 'concurrent/obligation'
|
13
|
-
require 'concurrent/promise'
|
14
|
-
require 'concurrent/supervisor'
|
15
|
-
require 'concurrent/utilities'
|
16
|
-
|
17
|
-
require 'concurrent/reactor'
|
18
|
-
require 'concurrent/reactor/drb_async_demux'
|
19
|
-
require 'concurrent/reactor/tcp_sync_demux'
|
20
|
-
|
21
|
-
require 'concurrent/thread_pool'
|
22
|
-
require 'concurrent/cached_thread_pool'
|
23
|
-
require 'concurrent/fixed_thread_pool'
|
24
|
-
require 'concurrent/null_thread_pool'
|
25
|
-
|
26
|
-
require 'concurrent/global_thread_pool'
|
27
|
-
|
28
|
-
require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/version'
|
4
|
+
|
5
|
+
require 'concurrent/event'
|
6
|
+
|
7
|
+
require 'concurrent/agent'
|
8
|
+
require 'concurrent/defer'
|
9
|
+
require 'concurrent/executor'
|
10
|
+
require 'concurrent/future'
|
11
|
+
require 'concurrent/goroutine'
|
12
|
+
require 'concurrent/obligation'
|
13
|
+
require 'concurrent/promise'
|
14
|
+
require 'concurrent/supervisor'
|
15
|
+
require 'concurrent/utilities'
|
16
|
+
|
17
|
+
require 'concurrent/reactor'
|
18
|
+
require 'concurrent/reactor/drb_async_demux'
|
19
|
+
require 'concurrent/reactor/tcp_sync_demux'
|
20
|
+
|
21
|
+
require 'concurrent/thread_pool'
|
22
|
+
require 'concurrent/cached_thread_pool'
|
23
|
+
require 'concurrent/fixed_thread_pool'
|
24
|
+
require 'concurrent/null_thread_pool'
|
25
|
+
|
26
|
+
require 'concurrent/global_thread_pool'
|
27
|
+
|
28
|
+
require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
|
data/lib/concurrent/agent.rb
CHANGED
@@ -1,114 +1,114 @@
|
|
1
|
-
require 'observer'
|
2
|
-
require 'thread'
|
3
|
-
|
4
|
-
require 'concurrent/global_thread_pool'
|
5
|
-
require 'concurrent/utilities'
|
6
|
-
|
7
|
-
module Concurrent
|
8
|
-
|
9
|
-
# An agent is a single atomic value that represents an identity. The current value
|
10
|
-
# of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
|
11
|
-
# the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
|
12
|
-
# will receive the current value of the agent as its sole parameter. The return value of the block
|
13
|
-
# will become the new value of the agent. Agents support two error handling modes: fail and continue.
|
14
|
-
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
15
|
-
class Agent
|
16
|
-
include Observable
|
17
|
-
include UsesGlobalThreadPool
|
18
|
-
|
19
|
-
TIMEOUT = 5
|
20
|
-
|
21
|
-
attr_reader :initial
|
22
|
-
attr_reader :timeout
|
23
|
-
|
24
|
-
def initialize(initial, timeout = TIMEOUT)
|
25
|
-
@value = initial
|
26
|
-
@timeout = timeout
|
27
|
-
@rescuers = []
|
28
|
-
@validator = nil
|
29
|
-
@queue = Queue.new
|
30
|
-
@mutex = Mutex.new
|
31
|
-
|
32
|
-
Agent.thread_pool.post{ work }
|
33
|
-
end
|
34
|
-
|
35
|
-
def value(timeout = 0) return @value; end
|
36
|
-
alias_method :deref, :value
|
37
|
-
|
38
|
-
def rescue(clazz = Exception, &block)
|
39
|
-
if block_given?
|
40
|
-
@mutex.synchronize do
|
41
|
-
@rescuers << Rescuer.new(clazz, block)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
return self
|
45
|
-
end
|
46
|
-
alias_method :catch, :rescue
|
47
|
-
alias_method :on_error, :rescue
|
48
|
-
|
49
|
-
def validate(&block)
|
50
|
-
@validator = block if block_given?
|
51
|
-
return self
|
52
|
-
end
|
53
|
-
alias_method :validates, :validate
|
54
|
-
alias_method :validate_with, :validate
|
55
|
-
alias_method :validates_with, :validate
|
56
|
-
|
57
|
-
def post(&block)
|
58
|
-
return @queue.length unless block_given?
|
59
|
-
return @mutex.synchronize do
|
60
|
-
@queue << block
|
61
|
-
@queue.length
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def <<(block)
|
66
|
-
self.post(&block)
|
67
|
-
return self
|
68
|
-
end
|
69
|
-
|
70
|
-
def length
|
71
|
-
return @queue.length
|
72
|
-
end
|
73
|
-
alias_method :size, :length
|
74
|
-
alias_method :count, :length
|
75
|
-
|
76
|
-
alias_method :add_watch, :add_observer
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
# @private
|
81
|
-
Rescuer = Struct.new(:clazz, :block)
|
82
|
-
|
83
|
-
# @private
|
84
|
-
def try_rescue(ex) # :nodoc:
|
85
|
-
rescuer = @mutex.synchronize do
|
86
|
-
@rescuers.find{|r| ex.is_a?(r.clazz) }
|
87
|
-
end
|
88
|
-
rescuer.block.call(ex) if rescuer
|
89
|
-
rescue Exception => e
|
90
|
-
# supress
|
91
|
-
end
|
92
|
-
|
93
|
-
# @private
|
94
|
-
def work # :nodoc:
|
95
|
-
loop do
|
96
|
-
handler = @queue.pop
|
97
|
-
begin
|
98
|
-
result = Timeout.timeout(@timeout) do
|
99
|
-
handler.call(@value)
|
100
|
-
end
|
101
|
-
if @validator.nil? || @validator.call(result)
|
102
|
-
@mutex.synchronize do
|
103
|
-
@value = result
|
104
|
-
changed
|
105
|
-
notify_observers(Time.now, @value)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
rescue Exception => ex
|
109
|
-
try_rescue(ex)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
1
|
+
require 'observer'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
require 'concurrent/global_thread_pool'
|
5
|
+
require 'concurrent/utilities'
|
6
|
+
|
7
|
+
module Concurrent
|
8
|
+
|
9
|
+
# An agent is a single atomic value that represents an identity. The current value
|
10
|
+
# of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
|
11
|
+
# the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
|
12
|
+
# will receive the current value of the agent as its sole parameter. The return value of the block
|
13
|
+
# will become the new value of the agent. Agents support two error handling modes: fail and continue.
|
14
|
+
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
15
|
+
class Agent
|
16
|
+
include Observable
|
17
|
+
include UsesGlobalThreadPool
|
18
|
+
|
19
|
+
TIMEOUT = 5
|
20
|
+
|
21
|
+
attr_reader :initial
|
22
|
+
attr_reader :timeout
|
23
|
+
|
24
|
+
def initialize(initial, timeout = TIMEOUT)
|
25
|
+
@value = initial
|
26
|
+
@timeout = timeout
|
27
|
+
@rescuers = []
|
28
|
+
@validator = nil
|
29
|
+
@queue = Queue.new
|
30
|
+
@mutex = Mutex.new
|
31
|
+
|
32
|
+
Agent.thread_pool.post{ work }
|
33
|
+
end
|
34
|
+
|
35
|
+
def value(timeout = 0) return @value; end
|
36
|
+
alias_method :deref, :value
|
37
|
+
|
38
|
+
def rescue(clazz = Exception, &block)
|
39
|
+
if block_given?
|
40
|
+
@mutex.synchronize do
|
41
|
+
@rescuers << Rescuer.new(clazz, block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return self
|
45
|
+
end
|
46
|
+
alias_method :catch, :rescue
|
47
|
+
alias_method :on_error, :rescue
|
48
|
+
|
49
|
+
def validate(&block)
|
50
|
+
@validator = block if block_given?
|
51
|
+
return self
|
52
|
+
end
|
53
|
+
alias_method :validates, :validate
|
54
|
+
alias_method :validate_with, :validate
|
55
|
+
alias_method :validates_with, :validate
|
56
|
+
|
57
|
+
def post(&block)
|
58
|
+
return @queue.length unless block_given?
|
59
|
+
return @mutex.synchronize do
|
60
|
+
@queue << block
|
61
|
+
@queue.length
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def <<(block)
|
66
|
+
self.post(&block)
|
67
|
+
return self
|
68
|
+
end
|
69
|
+
|
70
|
+
def length
|
71
|
+
return @queue.length
|
72
|
+
end
|
73
|
+
alias_method :size, :length
|
74
|
+
alias_method :count, :length
|
75
|
+
|
76
|
+
alias_method :add_watch, :add_observer
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# @private
|
81
|
+
Rescuer = Struct.new(:clazz, :block)
|
82
|
+
|
83
|
+
# @private
|
84
|
+
def try_rescue(ex) # :nodoc:
|
85
|
+
rescuer = @mutex.synchronize do
|
86
|
+
@rescuers.find{|r| ex.is_a?(r.clazz) }
|
87
|
+
end
|
88
|
+
rescuer.block.call(ex) if rescuer
|
89
|
+
rescue Exception => e
|
90
|
+
# supress
|
91
|
+
end
|
92
|
+
|
93
|
+
# @private
|
94
|
+
def work # :nodoc:
|
95
|
+
loop do
|
96
|
+
handler = @queue.pop
|
97
|
+
begin
|
98
|
+
result = Timeout.timeout(@timeout) do
|
99
|
+
handler.call(@value)
|
100
|
+
end
|
101
|
+
if @validator.nil? || @validator.call(result)
|
102
|
+
@mutex.synchronize do
|
103
|
+
@value = result
|
104
|
+
changed
|
105
|
+
notify_observers(Time.now, @value)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
rescue Exception => ex
|
109
|
+
try_rescue(ex)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -1,131 +1,131 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
require 'concurrent/thread_pool'
|
4
|
-
require 'concurrent/utilities'
|
5
|
-
|
6
|
-
require 'functional/utilities'
|
7
|
-
|
8
|
-
module Concurrent
|
9
|
-
|
10
|
-
def self.new_cached_thread_pool
|
11
|
-
return CachedThreadPool.new
|
12
|
-
end
|
13
|
-
|
14
|
-
class CachedThreadPool < ThreadPool
|
15
|
-
behavior(:thread_pool)
|
16
|
-
|
17
|
-
DEFAULT_GC_INTERVAL = 60
|
18
|
-
DEFAULT_THREAD_IDLETIME = 60
|
19
|
-
|
20
|
-
attr_reader :working
|
21
|
-
|
22
|
-
def initialize(opts = {})
|
23
|
-
@gc_interval = (opts[:gc_interval] || DEFAULT_GC_INTERVAL).freeze
|
24
|
-
@thread_idletime = (opts[:thread_idletime] || DEFAULT_THREAD_IDLETIME).freeze
|
25
|
-
super()
|
26
|
-
@working = 0
|
27
|
-
end
|
28
|
-
|
29
|
-
def kill
|
30
|
-
@status = :killed
|
31
|
-
mutex.synchronize do
|
32
|
-
@pool.each{|t| Thread.kill(t.thread) }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def size
|
37
|
-
return @pool.length
|
38
|
-
end
|
39
|
-
|
40
|
-
def post(*args, &block)
|
41
|
-
raise ArgumentError.new('no block given') unless block_given?
|
42
|
-
if running?
|
43
|
-
collect_garbage if @pool.empty?
|
44
|
-
mutex.synchronize do
|
45
|
-
if @working >= @pool.length
|
46
|
-
create_worker_thread
|
47
|
-
end
|
48
|
-
@queue << [args, block]
|
49
|
-
end
|
50
|
-
return true
|
51
|
-
else
|
52
|
-
return false
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# @private
|
57
|
-
def status # :nodoc:
|
58
|
-
mutex.synchronize do
|
59
|
-
@pool.collect do |worker|
|
60
|
-
[
|
61
|
-
worker.status,
|
62
|
-
worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
|
63
|
-
worker.thread.status
|
64
|
-
]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
private
|
70
|
-
|
71
|
-
Worker = Struct.new(:status, :idletime, :thread)
|
72
|
-
|
73
|
-
# @private
|
74
|
-
def create_worker_thread # :nodoc:
|
75
|
-
worker = Worker.new(:idle, timestamp, nil)
|
76
|
-
|
77
|
-
worker.thread = Thread.new(worker) do |me|
|
78
|
-
|
79
|
-
loop do
|
80
|
-
task = @queue.pop
|
81
|
-
|
82
|
-
mutex.synchronize do
|
83
|
-
@working += 1
|
84
|
-
me.status = :working
|
85
|
-
end
|
86
|
-
|
87
|
-
if task == :stop
|
88
|
-
me.status = :stopping
|
89
|
-
break
|
90
|
-
else
|
91
|
-
task.last.call(*task.first)
|
92
|
-
mutex.synchronize do
|
93
|
-
@working -= 1
|
94
|
-
me.status = :idle
|
95
|
-
me.idletime = timestamp
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
mutex.synchronize do
|
101
|
-
@pool.delete(me)
|
102
|
-
if @pool.empty?
|
103
|
-
@termination.set
|
104
|
-
@status = :shutdown unless killed?
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
worker.thread.abort_on_exception = false
|
110
|
-
@pool << worker
|
111
|
-
end
|
112
|
-
|
113
|
-
# @private
|
114
|
-
def collect_garbage # :nodoc:
|
115
|
-
@collector = Thread.new do
|
116
|
-
loop do
|
117
|
-
sleep(@gc_interval)
|
118
|
-
mutex.synchronize do
|
119
|
-
@pool.reject! do |worker|
|
120
|
-
worker.thread.status.nil? ||
|
121
|
-
(worker.status == :idle && @thread_idletime >= delta(worker.idletime, timestamp))
|
122
|
-
end
|
123
|
-
end
|
124
|
-
@working = @pool.count{|worker| worker.status == :working}
|
125
|
-
break if @pool.empty?
|
126
|
-
end
|
127
|
-
end
|
128
|
-
@collector.abort_on_exception = false
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/thread_pool'
|
4
|
+
require 'concurrent/utilities'
|
5
|
+
|
6
|
+
require 'functional/utilities'
|
7
|
+
|
8
|
+
module Concurrent
|
9
|
+
|
10
|
+
def self.new_cached_thread_pool
|
11
|
+
return CachedThreadPool.new
|
12
|
+
end
|
13
|
+
|
14
|
+
class CachedThreadPool < ThreadPool
|
15
|
+
behavior(:thread_pool)
|
16
|
+
|
17
|
+
DEFAULT_GC_INTERVAL = 60
|
18
|
+
DEFAULT_THREAD_IDLETIME = 60
|
19
|
+
|
20
|
+
attr_reader :working
|
21
|
+
|
22
|
+
def initialize(opts = {})
|
23
|
+
@gc_interval = (opts[:gc_interval] || DEFAULT_GC_INTERVAL).freeze
|
24
|
+
@thread_idletime = (opts[:thread_idletime] || DEFAULT_THREAD_IDLETIME).freeze
|
25
|
+
super()
|
26
|
+
@working = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def kill
|
30
|
+
@status = :killed
|
31
|
+
mutex.synchronize do
|
32
|
+
@pool.each{|t| Thread.kill(t.thread) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
return @pool.length
|
38
|
+
end
|
39
|
+
|
40
|
+
def post(*args, &block)
|
41
|
+
raise ArgumentError.new('no block given') unless block_given?
|
42
|
+
if running?
|
43
|
+
collect_garbage if @pool.empty?
|
44
|
+
mutex.synchronize do
|
45
|
+
if @working >= @pool.length
|
46
|
+
create_worker_thread
|
47
|
+
end
|
48
|
+
@queue << [args, block]
|
49
|
+
end
|
50
|
+
return true
|
51
|
+
else
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
def status # :nodoc:
|
58
|
+
mutex.synchronize do
|
59
|
+
@pool.collect do |worker|
|
60
|
+
[
|
61
|
+
worker.status,
|
62
|
+
worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
|
63
|
+
worker.thread.status
|
64
|
+
]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
Worker = Struct.new(:status, :idletime, :thread)
|
72
|
+
|
73
|
+
# @private
|
74
|
+
def create_worker_thread # :nodoc:
|
75
|
+
worker = Worker.new(:idle, timestamp, nil)
|
76
|
+
|
77
|
+
worker.thread = Thread.new(worker) do |me|
|
78
|
+
|
79
|
+
loop do
|
80
|
+
task = @queue.pop
|
81
|
+
|
82
|
+
mutex.synchronize do
|
83
|
+
@working += 1
|
84
|
+
me.status = :working
|
85
|
+
end
|
86
|
+
|
87
|
+
if task == :stop
|
88
|
+
me.status = :stopping
|
89
|
+
break
|
90
|
+
else
|
91
|
+
task.last.call(*task.first)
|
92
|
+
mutex.synchronize do
|
93
|
+
@working -= 1
|
94
|
+
me.status = :idle
|
95
|
+
me.idletime = timestamp
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
mutex.synchronize do
|
101
|
+
@pool.delete(me)
|
102
|
+
if @pool.empty?
|
103
|
+
@termination.set
|
104
|
+
@status = :shutdown unless killed?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
worker.thread.abort_on_exception = false
|
110
|
+
@pool << worker
|
111
|
+
end
|
112
|
+
|
113
|
+
# @private
|
114
|
+
def collect_garbage # :nodoc:
|
115
|
+
@collector = Thread.new do
|
116
|
+
loop do
|
117
|
+
sleep(@gc_interval)
|
118
|
+
mutex.synchronize do
|
119
|
+
@pool.reject! do |worker|
|
120
|
+
worker.thread.status.nil? ||
|
121
|
+
(worker.status == :idle && @thread_idletime >= delta(worker.idletime, timestamp))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
@working = @pool.count{|worker| worker.status == :working}
|
125
|
+
break if @pool.empty?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
@collector.abort_on_exception = false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|