concurrent-ruby 0.2.2 → 0.3.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +45 -42
- data/lib/concurrent.rb +5 -6
- data/lib/concurrent/agent.rb +29 -33
- data/lib/concurrent/cached_thread_pool.rb +26 -105
- data/lib/concurrent/channel.rb +94 -0
- data/lib/concurrent/event.rb +8 -17
- data/lib/concurrent/executor.rb +68 -72
- data/lib/concurrent/fixed_thread_pool.rb +15 -83
- data/lib/concurrent/functions.rb +7 -22
- data/lib/concurrent/future.rb +29 -9
- data/lib/concurrent/null_thread_pool.rb +5 -2
- data/lib/concurrent/obligation.rb +6 -16
- data/lib/concurrent/promise.rb +9 -10
- data/lib/concurrent/runnable.rb +103 -0
- data/lib/concurrent/supervisor.rb +271 -44
- data/lib/concurrent/thread_pool.rb +112 -39
- data/lib/concurrent/version.rb +1 -1
- data/md/executor.md +9 -3
- data/md/goroutine.md +11 -9
- data/md/reactor.md +32 -0
- data/md/supervisor.md +43 -0
- data/spec/concurrent/agent_spec.rb +128 -51
- data/spec/concurrent/cached_thread_pool_spec.rb +33 -47
- data/spec/concurrent/channel_spec.rb +446 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +3 -1
- data/spec/concurrent/event_spec.rb +0 -19
- data/spec/concurrent/executor_spec.rb +167 -119
- data/spec/concurrent/fixed_thread_pool_spec.rb +40 -30
- data/spec/concurrent/functions_spec.rb +0 -20
- data/spec/concurrent/future_spec.rb +88 -0
- data/spec/concurrent/null_thread_pool_spec.rb +23 -2
- data/spec/concurrent/obligation_shared.rb +0 -5
- data/spec/concurrent/promise_spec.rb +9 -10
- data/spec/concurrent/runnable_shared.rb +62 -0
- data/spec/concurrent/runnable_spec.rb +233 -0
- data/spec/concurrent/supervisor_spec.rb +912 -47
- data/spec/concurrent/thread_pool_shared.rb +18 -31
- data/spec/spec_helper.rb +10 -3
- metadata +17 -23
- data/lib/concurrent/defer.rb +0 -65
- data/lib/concurrent/reactor.rb +0 -166
- data/lib/concurrent/reactor/drb_async_demux.rb +0 -83
- data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -131
- data/lib/concurrent/utilities.rb +0 -32
- data/md/defer.md +0 -174
- data/spec/concurrent/defer_spec.rb +0 -199
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -196
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -410
- data/spec/concurrent/reactor_spec.rb +0 -364
- data/spec/concurrent/utilities_spec.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed299ebc57fa2caa62d5538e28f6506d175be4f0
|
4
|
+
data.tar.gz: 4e160d0efeefab15be0c1767679079f9d07b5b59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5687e0000024468f66f42cfd4742ff672502ed6f9cde7a9579a84acfdcdfde8af33b2df28a0b387f36767bfefe3d70411b8efa7a5ba8cb3f7a684112db5a11ec
|
7
|
+
data.tar.gz: d8e45ab66ecb79ad64310e8a31f604c146840d9abf5bd51e9dcbf48279767dd0c9a02fad2f37e5bef25f9ec5ca656d92bf87ccbcced9e2aa7ff9bc8e9e47fca5
|
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
# Concurrent Ruby [![Build Status](https://secure.travis-ci.org/jdantonio/concurrent-ruby.png)](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [![Dependency Status](https://gemnasium.com/jdantonio/concurrent-ruby.png)](https://gemnasium.com/jdantonio/concurrent-ruby)
|
1
|
+
# Concurrent Ruby [![Build Status](https://secure.travis-ci.org/jdantonio/concurrent-ruby.png)](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [![Coverage Status](https://coveralls.io/repos/jdantonio/concurrent-ruby/badge.png)](https://coveralls.io/r/jdantonio/concurrent-ruby) [![Dependency Status](https://gemnasium.com/jdantonio/concurrent-ruby.png)](https://gemnasium.com/jdantonio/concurrent-ruby)
|
2
2
|
|
3
|
-
Modern concurrency tools including agents, futures, promises, thread pools,
|
3
|
+
Modern concurrency tools including agents, futures, promises, thread pools, supervisors, and more.
|
4
4
|
Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency patterns.
|
5
5
|
|
6
|
+
If you find this gem useful you should check out my [functional-ruby](https://github.com/jdantonio/functional-ruby)
|
7
|
+
gem, too. This gem uses several of the tools in that gem.
|
8
|
+
|
6
9
|
## Introduction
|
7
10
|
|
8
11
|
The old-school "lock and synchronize" approach to concurrency is dead. The future of concurrency
|
@@ -51,12 +54,13 @@ Specifically:
|
|
51
54
|
Several features from Erlang, Go, Clojure, Java, and JavaScript have been implemented thus far:
|
52
55
|
|
53
56
|
* Clojure inspired [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
|
54
|
-
* EventMachine inspired [Defer](https://github.com/jdantonio/concurrent-ruby/blob/master/md/defer.md)
|
55
57
|
* Clojure inspired [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
|
56
58
|
* Go inspired [Goroutine](https://github.com/jdantonio/concurrent-ruby/blob/master/md/goroutine.md)
|
57
59
|
* JavaScript inspired [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
|
58
60
|
* Java inspired [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
|
61
|
+
* Old school [events](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655(v=vs.85).aspx) from back in my Visual C++ days
|
59
62
|
* Scheduled task execution with the [Executor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/executor.md) service
|
63
|
+
* Erlang-inspired [Supervisor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md) for managing long-running threads
|
60
64
|
|
61
65
|
### Is it any good?
|
62
66
|
|
@@ -118,10 +122,17 @@ For complete examples, see the specific documentation linked above. Below are a
|
|
118
122
|
```ruby
|
119
123
|
require 'concurrent'
|
120
124
|
|
121
|
-
|
122
|
-
go(
|
123
|
-
sleep(0.1)
|
124
|
-
|
125
|
+
go('foo'){|echo| sleep(0.1); print "#{echo}\n"; sleep(0.1); print "Boom!\n" }
|
126
|
+
go('bar'){|echo| sleep(0.1); print "#{echo}\n"; sleep(0.1); print "Pow!\n" }
|
127
|
+
go('baz'){|echo| sleep(0.1); print "#{echo}\n"; sleep(0.1); print "Zap!\n" }
|
128
|
+
sleep(0.5)
|
129
|
+
|
130
|
+
#=> foo
|
131
|
+
#=> bar
|
132
|
+
#=> baz
|
133
|
+
#=> Boom!
|
134
|
+
#=> Pow!
|
135
|
+
#=> Zap!
|
125
136
|
```
|
126
137
|
|
127
138
|
#### Agent (Clojure)
|
@@ -146,28 +157,6 @@ sleep(0.1)
|
|
146
157
|
score.value #=> 170
|
147
158
|
```
|
148
159
|
|
149
|
-
#### Defer (EventMachine)
|
150
|
-
|
151
|
-
```ruby
|
152
|
-
require 'concurrent'
|
153
|
-
require 'concurrent/functions'
|
154
|
-
|
155
|
-
Concurrent::Defer.new{ "Jerry D'Antonio" }.
|
156
|
-
then{|result| puts "Hello, #{result}!" }.
|
157
|
-
rescue{|ex| puts ex.message }.
|
158
|
-
go
|
159
|
-
|
160
|
-
#=> Hello, Jerry D'Antonio!
|
161
|
-
|
162
|
-
operation = proc{ raise StandardError.new('Boom!') }
|
163
|
-
callback = proc{|result| puts result }
|
164
|
-
errorback = proc{|ex| puts ex.message }
|
165
|
-
defer(operation, callback, errorback)
|
166
|
-
sleep(0.1)
|
167
|
-
|
168
|
-
#=> "Boom!"
|
169
|
-
```
|
170
|
-
|
171
160
|
#### Future (Clojure)
|
172
161
|
|
173
162
|
```ruby
|
@@ -200,23 +189,37 @@ p.value #=> "Hello Jerry D'Antonio. Would you like to play a game?"
|
|
200
189
|
```ruby
|
201
190
|
require 'concurrent'
|
202
191
|
|
203
|
-
pool = Concurrent::FixedThreadPool.new(
|
204
|
-
|
205
|
-
|
206
|
-
pool.post{ sleep(0.5);
|
207
|
-
pool.
|
208
|
-
|
192
|
+
pool = Concurrent::FixedThreadPool.new(2)
|
193
|
+
pool.size #=> 2
|
194
|
+
|
195
|
+
pool.post{ sleep(0.5); print "Boom!\n" }
|
196
|
+
pool.size #=> 2
|
197
|
+
pool.post{ sleep(0.5); print "Pow!\n" }
|
198
|
+
pool.size #=> 2
|
199
|
+
pool.post{ sleep(0.5); print "Zap!\n" }
|
200
|
+
pool.size #=> 2
|
201
|
+
|
209
202
|
sleep(1)
|
210
|
-
|
203
|
+
|
204
|
+
#=> Boom!
|
205
|
+
#=> Pow!
|
206
|
+
#=> Zap!
|
211
207
|
|
212
208
|
pool = Concurrent::CachedThreadPool.new
|
213
|
-
|
214
|
-
|
215
|
-
pool << proc{ sleep(0.5);
|
216
|
-
pool
|
217
|
-
|
209
|
+
pool.size #=> 0
|
210
|
+
|
211
|
+
pool << proc{ sleep(0.5); print "Boom!\n" }
|
212
|
+
pool.size #=> 1
|
213
|
+
pool << proc{ sleep(0.5); print "Pow!\n" }
|
214
|
+
pool.size #=> 2
|
215
|
+
pool << proc{ sleep(0.5); print "Zap!\n" }
|
216
|
+
pool.size #=> 3
|
217
|
+
|
218
218
|
sleep(1)
|
219
|
-
|
219
|
+
|
220
|
+
#=> Boom!
|
221
|
+
#=> Pow!
|
222
|
+
#=> Zap!
|
220
223
|
```
|
221
224
|
|
222
225
|
#### Executor
|
data/lib/concurrent.rb
CHANGED
@@ -1,22 +1,21 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
+
$ENABLE_BEHAVIOR_CHECK_ON_CONSTRUCTION ||= ( $DEGUG ? true : false )
|
4
|
+
require 'functional'
|
5
|
+
|
3
6
|
require 'concurrent/version'
|
4
7
|
|
5
8
|
require 'concurrent/event'
|
6
9
|
|
7
10
|
require 'concurrent/agent'
|
8
|
-
require 'concurrent/
|
11
|
+
require 'concurrent/channel'
|
9
12
|
require 'concurrent/executor'
|
10
13
|
require 'concurrent/future'
|
11
14
|
require 'concurrent/goroutine'
|
12
15
|
require 'concurrent/obligation'
|
13
16
|
require 'concurrent/promise'
|
17
|
+
require 'concurrent/runnable'
|
14
18
|
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
19
|
|
21
20
|
require 'concurrent/thread_pool'
|
22
21
|
require 'concurrent/cached_thread_pool'
|
data/lib/concurrent/agent.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
require 'observer'
|
2
1
|
require 'thread'
|
2
|
+
require 'observer'
|
3
3
|
|
4
4
|
require 'concurrent/global_thread_pool'
|
5
|
-
require 'concurrent/utilities'
|
6
5
|
|
7
6
|
module Concurrent
|
8
7
|
|
@@ -21,21 +20,31 @@ module Concurrent
|
|
21
20
|
attr_reader :initial
|
22
21
|
attr_reader :timeout
|
23
22
|
|
24
|
-
def initialize(initial,
|
23
|
+
def initialize(initial, opts = {})
|
25
24
|
@value = initial
|
26
|
-
@timeout = timeout
|
27
25
|
@rescuers = []
|
28
26
|
@validator = nil
|
29
|
-
@queue = Queue.new
|
30
|
-
@mutex = Mutex.new
|
31
27
|
|
32
|
-
|
28
|
+
@timeout = opts[:timeout] || TIMEOUT
|
29
|
+
@dup_on_deref = opts[:dup_on_deref] || opts[:dup] || false
|
30
|
+
@freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze] || false
|
31
|
+
@copy_on_deref = opts[:copy_on_deref] || opts[:copy]
|
32
|
+
|
33
|
+
@mutex = Mutex.new
|
33
34
|
end
|
34
35
|
|
35
|
-
def value(timeout = 0)
|
36
|
+
def value(timeout = 0)
|
37
|
+
return @mutex.synchronize do
|
38
|
+
value = @value
|
39
|
+
value = @copy_on_deref.call(value) if @copy_on_deref
|
40
|
+
value = value.dup if @dup_on_deref
|
41
|
+
value = value.freeze if @freeze_on_deref
|
42
|
+
value
|
43
|
+
end
|
44
|
+
end
|
36
45
|
alias_method :deref, :value
|
37
46
|
|
38
|
-
def rescue(clazz =
|
47
|
+
def rescue(clazz = nil, &block)
|
39
48
|
if block_given?
|
40
49
|
@mutex.synchronize do
|
41
50
|
@rescuers << Rescuer.new(clazz, block)
|
@@ -55,11 +64,7 @@ module Concurrent
|
|
55
64
|
alias_method :validates_with, :validate
|
56
65
|
|
57
66
|
def post(&block)
|
58
|
-
|
59
|
-
return @mutex.synchronize do
|
60
|
-
@queue << block
|
61
|
-
@queue.length
|
62
|
-
end
|
67
|
+
Agent.thread_pool.post{ work(&block) } if block_given?
|
63
68
|
end
|
64
69
|
|
65
70
|
def <<(block)
|
@@ -67,12 +72,6 @@ module Concurrent
|
|
67
72
|
return self
|
68
73
|
end
|
69
74
|
|
70
|
-
def length
|
71
|
-
return @queue.length
|
72
|
-
end
|
73
|
-
alias_method :size, :length
|
74
|
-
alias_method :count, :length
|
75
|
-
|
76
75
|
alias_method :add_watch, :add_observer
|
77
76
|
|
78
77
|
private
|
@@ -83,31 +82,28 @@ module Concurrent
|
|
83
82
|
# @private
|
84
83
|
def try_rescue(ex) # :nodoc:
|
85
84
|
rescuer = @mutex.synchronize do
|
86
|
-
@rescuers.find{|r| ex.is_a?(r.clazz) }
|
85
|
+
@rescuers.find{|r| r.clazz.nil? || ex.is_a?(r.clazz) }
|
87
86
|
end
|
88
87
|
rescuer.block.call(ex) if rescuer
|
89
|
-
rescue Exception =>
|
88
|
+
rescue Exception => ex
|
90
89
|
# supress
|
91
90
|
end
|
92
91
|
|
93
92
|
# @private
|
94
|
-
def work # :nodoc:
|
95
|
-
|
96
|
-
|
97
|
-
begin
|
93
|
+
def work(&handler) # :nodoc:
|
94
|
+
begin
|
95
|
+
@mutex.synchronize do
|
98
96
|
result = Timeout.timeout(@timeout) do
|
99
97
|
handler.call(@value)
|
100
98
|
end
|
101
99
|
if @validator.nil? || @validator.call(result)
|
102
|
-
@
|
103
|
-
|
104
|
-
|
105
|
-
notify_observers(Time.now, @value)
|
106
|
-
end
|
100
|
+
@value = result
|
101
|
+
changed
|
102
|
+
notify_observers(Time.now, @value)
|
107
103
|
end
|
108
|
-
rescue Exception => ex
|
109
|
-
try_rescue(ex)
|
110
104
|
end
|
105
|
+
rescue Exception => ex
|
106
|
+
try_rescue(ex)
|
111
107
|
end
|
112
108
|
end
|
113
109
|
end
|
@@ -1,131 +1,52 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
1
|
require 'concurrent/thread_pool'
|
4
|
-
require 'concurrent/utilities'
|
5
|
-
|
6
|
-
require 'functional/utilities'
|
7
2
|
|
8
3
|
module Concurrent
|
9
4
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
5
|
+
class CachedThreadPool < AbstractThreadPool
|
6
|
+
behavior(:global_thread_pool)
|
13
7
|
|
14
|
-
class CachedThreadPool < ThreadPool
|
15
|
-
behavior(:thread_pool)
|
16
|
-
|
17
|
-
DEFAULT_GC_INTERVAL = 60
|
18
8
|
DEFAULT_THREAD_IDLETIME = 60
|
19
9
|
|
20
|
-
attr_reader :working
|
21
|
-
|
22
10
|
def initialize(opts = {})
|
23
|
-
@
|
24
|
-
|
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
|
11
|
+
@idletime = (opts[:idletime] || DEFAULT_THREAD_IDLETIME).to_i
|
12
|
+
super(opts)
|
38
13
|
end
|
39
14
|
|
40
15
|
def post(*args, &block)
|
41
16
|
raise ArgumentError.new('no block given') unless block_given?
|
42
|
-
|
43
|
-
|
44
|
-
mutex.synchronize do
|
45
|
-
if @working >= @pool.length
|
46
|
-
create_worker_thread
|
47
|
-
end
|
17
|
+
return @mutex.synchronize do
|
18
|
+
if @state == :running
|
48
19
|
@queue << [args, block]
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
]
|
20
|
+
at_capacity = @pool.empty? || ! @queue.empty? || @working >= @pool.size
|
21
|
+
if at_capacity && @pool.length < @max_threads
|
22
|
+
@pool << create_worker_thread
|
23
|
+
end
|
24
|
+
true
|
25
|
+
else
|
26
|
+
false
|
65
27
|
end
|
66
28
|
end
|
67
29
|
end
|
68
30
|
|
69
|
-
|
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
|
31
|
+
protected
|
81
32
|
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
33
|
+
def dead_worker?(context)
|
34
|
+
return context.thread.nil? || context.thread.status == 'aborting' || ! context.thread.status
|
35
|
+
end
|
99
36
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
37
|
+
def stale_worker?(context)
|
38
|
+
if context.status == :idle && @idletime <= (timestamp - context.idletime)
|
39
|
+
context.thread.kill
|
40
|
+
return true
|
41
|
+
else
|
42
|
+
return false
|
107
43
|
end
|
108
|
-
|
109
|
-
worker.thread.abort_on_exception = false
|
110
|
-
@pool << worker
|
111
44
|
end
|
112
45
|
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
46
|
+
def collect_garbage
|
47
|
+
@pool.reject! do |context|
|
48
|
+
dead_worker?(context) || stale_worker?(context)
|
127
49
|
end
|
128
|
-
@collector.abort_on_exception = false
|
129
50
|
end
|
130
51
|
end
|
131
52
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
require 'concurrent/runnable'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
class Channel
|
9
|
+
include Observable
|
10
|
+
include Runnable
|
11
|
+
behavior(:runnable)
|
12
|
+
|
13
|
+
def initialize(errorback = nil, &block)
|
14
|
+
@queue = Queue.new
|
15
|
+
@task = block
|
16
|
+
@errorback = errorback
|
17
|
+
end
|
18
|
+
|
19
|
+
def post(*message)
|
20
|
+
return false unless running?
|
21
|
+
@queue.push(message)
|
22
|
+
return @queue.length
|
23
|
+
end
|
24
|
+
|
25
|
+
def <<(message)
|
26
|
+
post(*message)
|
27
|
+
return self
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.pool(count, errorback = nil, &block)
|
31
|
+
raise ArgumentError.new('count must be greater than zero') unless count > 0
|
32
|
+
mailbox = Queue.new
|
33
|
+
channels = count.times.collect do
|
34
|
+
channel = self.new(errorback, &block)
|
35
|
+
channel.instance_variable_set(:@queue, mailbox)
|
36
|
+
channel
|
37
|
+
end
|
38
|
+
return Poolbox.new(mailbox), channels
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
class Poolbox
|
44
|
+
|
45
|
+
def initialize(queue)
|
46
|
+
@queue = queue
|
47
|
+
end
|
48
|
+
|
49
|
+
def post(*message)
|
50
|
+
@queue.push(message)
|
51
|
+
return @queue.length
|
52
|
+
end
|
53
|
+
|
54
|
+
def <<(message)
|
55
|
+
post(*message)
|
56
|
+
return self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @private
|
61
|
+
def on_run # :nodoc:
|
62
|
+
@queue.clear
|
63
|
+
end
|
64
|
+
|
65
|
+
# @private
|
66
|
+
def on_stop # :nodoc:
|
67
|
+
@queue.clear
|
68
|
+
@queue.push(:stop)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @private
|
72
|
+
def on_task # :nodoc:
|
73
|
+
message = @queue.pop
|
74
|
+
return if message == :stop
|
75
|
+
begin
|
76
|
+
result = receive(*message)
|
77
|
+
changed
|
78
|
+
notify_observers(Time.now, message, result)
|
79
|
+
rescue => ex
|
80
|
+
on_error(Time.now, message, ex)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @private
|
85
|
+
def on_error(time, msg, ex) # :nodoc:
|
86
|
+
@errorback.call(time, msg, ex) if @errorback
|
87
|
+
end
|
88
|
+
|
89
|
+
# @private
|
90
|
+
def receive(*message) # :nodoc:
|
91
|
+
@task.call(*message) unless @task.nil?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|