concurrent-ruby 0.3.0 → 0.3.1.pre.1
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 +4 -4
- data/README.md +55 -5
- data/lib/concurrent.rb +3 -0
- data/lib/concurrent/actor.rb +90 -36
- data/lib/concurrent/agent.rb +7 -21
- data/lib/concurrent/contract.rb +20 -0
- data/lib/concurrent/dereferenceable.rb +33 -0
- data/lib/concurrent/future.rb +7 -6
- data/lib/concurrent/obligation.rb +4 -3
- data/lib/concurrent/promise.rb +3 -3
- data/lib/concurrent/scheduled_task.rb +94 -0
- data/lib/concurrent/version.rb +1 -1
- data/md/actor.md +209 -0
- data/md/agent.md +30 -11
- data/md/future.md +55 -13
- data/md/scheduled_task.md +34 -0
- data/md/supervisor.md +209 -6
- data/md/timer_task.md +1 -1
- data/spec/concurrent/actor_spec.rb +244 -48
- data/spec/concurrent/agent_spec.rb +52 -1
- data/spec/concurrent/contract_spec.rb +34 -0
- data/spec/concurrent/future_spec.rb +6 -1
- data/spec/concurrent/obligation_shared.rb +1 -1
- data/spec/concurrent/promise_spec.rb +6 -1
- data/spec/concurrent/runnable_shared.rb +1 -1
- data/spec/concurrent/scheduled_task_spec.rb +259 -0
- metadata +14 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86c7da12ad6b46036abb100e98a1450eda80fbb1
|
4
|
+
data.tar.gz: 43e1758062fd717a6e4a0c690890ef8149aa7b9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d46489016358e9138bf0a627617ef780d2feb8e9a0d2b1f2a4ac1aa25ff8ec94dab7c72ad5c62743adc8d6ce36b0b119d80c3aa57724685aa33788e541f2aea
|
7
|
+
data.tar.gz: b482b8b50ee0217bb0ed13dcb8d3c91f58984d660f91240484d867f72e7583b87fcde09558beb1349ecff39ba88e9d9ddc44863cb91f14a74d4109cf1ebcac05
|
data/README.md
CHANGED
@@ -6,6 +6,18 @@ Inspired by Erlang, Clojure, Go, JavaScript, actors, and classic concurrency pat
|
|
6
6
|
If you find this gem useful you should check out my [functional-ruby](https://github.com/jdantonio/functional-ruby)
|
7
7
|
gem, too. This gem uses several of the tools in that gem.
|
8
8
|
|
9
|
+
## Conference Presentations
|
10
|
+
|
11
|
+
I've given several conference presentations on concurrent programming with this gem.
|
12
|
+
Check them out:
|
13
|
+
|
14
|
+
* ["Advanced Concurrent Programming in Ruby"](http://rubyconf.org/program#jerry-dantonio)
|
15
|
+
at [RubyConf 2013](http://rubyconf.org/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation) version of the presentation
|
16
|
+
* ["Advanced Multithreading in Ruby"](http://cascadiaruby.com/#advanced-multithreading-in-ruby)
|
17
|
+
at [Cascadia Ruby 2013](http://cascadiaruby.com/) used [this](https://github.com/jdantonio/concurrent-ruby-presentation/tree/cascadia-ruby-2013) version of the presentation
|
18
|
+
* I'll be giving ["Advanced Concurrent Programming in Ruby"](http://codemash.org/sessions)
|
19
|
+
at [CodeMash 2014](http://codemash.org/)
|
20
|
+
|
9
21
|
## Introduction
|
10
22
|
|
11
23
|
The old-school "lock and synchronize" approach to concurrency is dead. The future of concurrency
|
@@ -55,12 +67,13 @@ Several features from Erlang, Go, Clojure, Java, and JavaScript have been implem
|
|
55
67
|
|
56
68
|
* Clojure inspired [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
|
57
69
|
* Clojure inspired [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
|
58
|
-
* Scala inspired [Actor](
|
70
|
+
* Scala inspired [Actor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/actor.md)
|
59
71
|
* Go inspired [Goroutine](https://github.com/jdantonio/concurrent-ruby/blob/master/md/goroutine.md)
|
60
72
|
* JavaScript inspired [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
|
61
73
|
* Java inspired [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
|
62
74
|
* Old school [events](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx) from back in my Visual C++ days
|
63
75
|
* Repeated task execution with Java inspired [TimerTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/timer_task.md) service
|
76
|
+
* Scheduled task execution with Java inspired [ScheduledTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/scheduled_task.md) service
|
64
77
|
* Erlang inspired [Supervisor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md) for managing long-running threads
|
65
78
|
|
66
79
|
### Is it any good?
|
@@ -161,7 +174,7 @@ sleep(1)
|
|
161
174
|
p.value #=> "Hello Jerry D'Antonio. Would you like to play a game?"
|
162
175
|
```
|
163
176
|
|
164
|
-
#### Thread Pools
|
177
|
+
#### Thread Pools (Java)
|
165
178
|
|
166
179
|
```ruby
|
167
180
|
require 'concurrent'
|
@@ -199,7 +212,7 @@ sleep(1)
|
|
199
212
|
#=> Zap!
|
200
213
|
```
|
201
214
|
|
202
|
-
#### TimerTask
|
215
|
+
#### TimerTask (Java)
|
203
216
|
|
204
217
|
```ruby
|
205
218
|
require 'concurrent'
|
@@ -216,10 +229,47 @@ ec.status #=> "sleep"
|
|
216
229
|
ec.kill #=> true
|
217
230
|
```
|
218
231
|
|
232
|
+
#### Actor (Scala)
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
class FinanceActor < Concurrent::Actor
|
236
|
+
def act(query)
|
237
|
+
finance = Finance.new(query)
|
238
|
+
print "[#{Time.now}] RECEIVED '#{query}' to #{self} returned #{finance.update.suggested_symbols}\n\n"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
financial, pool = FinanceActor.pool(5)
|
243
|
+
|
244
|
+
pool << 'YAHOO'
|
245
|
+
pool << 'Micosoft'
|
246
|
+
pool << 'google'
|
247
|
+
```
|
248
|
+
|
249
|
+
#### Supervisor (Erlang)
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
pong = Pong.new
|
253
|
+
ping = Ping.new(10000, pong)
|
254
|
+
pong.ping = ping
|
255
|
+
|
256
|
+
task = Concurrent::TimerTask.new{ print "Boom!\n" }
|
257
|
+
|
258
|
+
boss = Concurrent::Supervisor.new
|
259
|
+
boss.add_worker(ping)
|
260
|
+
boss.add_worker(pong)
|
261
|
+
boss.add_worker(task)
|
262
|
+
|
263
|
+
boss.run!
|
264
|
+
|
265
|
+
ping << :pong
|
266
|
+
```
|
267
|
+
|
219
268
|
## Todo
|
220
269
|
|
221
|
-
*
|
222
|
-
*
|
270
|
+
* [Task Parallel Library (TPL)](http://msdn.microsoft.com/en-us/library/dd460717.aspx)
|
271
|
+
* [Data Parallelism](http://msdn.microsoft.com/en-us/library/dd537608.aspx)
|
272
|
+
* [Task Parallelism](http://msdn.microsoft.com/en-us/library/dd537609.aspx)
|
223
273
|
* More Erlang goodness
|
224
274
|
* gen_server
|
225
275
|
* gen_event
|
data/lib/concurrent.rb
CHANGED
@@ -3,12 +3,15 @@ require 'concurrent/version'
|
|
3
3
|
|
4
4
|
require 'concurrent/actor'
|
5
5
|
require 'concurrent/agent'
|
6
|
+
require 'concurrent/contract'
|
7
|
+
require 'concurrent/dereferenceable'
|
6
8
|
require 'concurrent/event'
|
7
9
|
require 'concurrent/future'
|
8
10
|
require 'concurrent/goroutine'
|
9
11
|
require 'concurrent/obligation'
|
10
12
|
require 'concurrent/promise'
|
11
13
|
require 'concurrent/runnable'
|
14
|
+
require 'concurrent/scheduled_task'
|
12
15
|
require 'concurrent/supervisor'
|
13
16
|
require 'concurrent/timer_task'
|
14
17
|
require 'concurrent/utilities'
|
data/lib/concurrent/actor.rb
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'observer'
|
3
3
|
|
4
|
+
require 'concurrent/event'
|
5
|
+
require 'concurrent/obligation'
|
4
6
|
require 'concurrent/runnable'
|
5
7
|
|
6
8
|
module Concurrent
|
7
9
|
|
8
|
-
|
9
|
-
class Actor
|
10
|
-
include Observable
|
11
|
-
include Runnable
|
10
|
+
module Postable
|
12
11
|
|
13
|
-
|
14
|
-
@queue = Queue.new
|
15
|
-
end
|
12
|
+
Package = Struct.new(:message, :handler, :notifier)
|
16
13
|
|
17
14
|
def post(*message)
|
18
|
-
return false unless
|
19
|
-
|
20
|
-
return
|
15
|
+
return false unless ready?
|
16
|
+
queue.push(Package.new(message))
|
17
|
+
return queue.length
|
21
18
|
end
|
22
19
|
|
23
20
|
def <<(message)
|
@@ -25,6 +22,66 @@ module Concurrent
|
|
25
22
|
return self
|
26
23
|
end
|
27
24
|
|
25
|
+
def post?(*message)
|
26
|
+
return nil unless ready?
|
27
|
+
contract = Contract.new
|
28
|
+
queue.push(Package.new(message, contract))
|
29
|
+
return contract
|
30
|
+
end
|
31
|
+
|
32
|
+
def post!(seconds, *message)
|
33
|
+
raise Concurrent::Runnable::LifecycleError unless ready?
|
34
|
+
raise Concurrent::TimeoutError if seconds.to_f <= 0.0
|
35
|
+
event = Event.new
|
36
|
+
cback = Queue.new
|
37
|
+
queue.push(Package.new(message, cback, event))
|
38
|
+
if event.wait(seconds)
|
39
|
+
result = cback.pop
|
40
|
+
if result.is_a?(Exception)
|
41
|
+
raise result
|
42
|
+
else
|
43
|
+
return result
|
44
|
+
end
|
45
|
+
else
|
46
|
+
event.set # attempt to cancel
|
47
|
+
raise Concurrent::TimeoutError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def forward(receiver, *message)
|
52
|
+
return false unless ready?
|
53
|
+
queue.push(Package.new(message, receiver))
|
54
|
+
return queue.length
|
55
|
+
end
|
56
|
+
|
57
|
+
def ready?
|
58
|
+
if self.respond_to?(:running?) && ! running?
|
59
|
+
return false
|
60
|
+
else
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def queue
|
68
|
+
@queue ||= Queue.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Actor
|
73
|
+
include Observable
|
74
|
+
include Runnable
|
75
|
+
include Postable
|
76
|
+
|
77
|
+
class Poolbox
|
78
|
+
include Postable
|
79
|
+
|
80
|
+
def initialize(queue)
|
81
|
+
@queue = queue
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
28
85
|
def self.pool(count, &block)
|
29
86
|
raise ArgumentError.new('count must be greater than zero') unless count > 0
|
30
87
|
mailbox = Queue.new
|
@@ -42,44 +99,41 @@ module Concurrent
|
|
42
99
|
raise NotImplementedError.new("#{self.class} does not implement #act")
|
43
100
|
end
|
44
101
|
|
45
|
-
class Poolbox
|
46
|
-
|
47
|
-
def initialize(queue)
|
48
|
-
@queue = queue
|
49
|
-
end
|
50
|
-
|
51
|
-
def post(*message)
|
52
|
-
@queue.push(message)
|
53
|
-
return @queue.length
|
54
|
-
end
|
55
|
-
|
56
|
-
def <<(message)
|
57
|
-
post(*message)
|
58
|
-
return self
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
102
|
# @private
|
63
103
|
def on_run # :nodoc:
|
64
|
-
|
104
|
+
queue.clear
|
65
105
|
end
|
66
106
|
|
67
107
|
# @private
|
68
108
|
def on_stop # :nodoc:
|
69
|
-
|
70
|
-
|
109
|
+
queue.clear
|
110
|
+
queue.push(:stop)
|
71
111
|
end
|
72
112
|
|
73
113
|
# @private
|
74
114
|
def on_task # :nodoc:
|
75
|
-
|
76
|
-
return if
|
115
|
+
package = queue.pop
|
116
|
+
return if package == :stop
|
117
|
+
result = ex = nil
|
118
|
+
notifier = package.notifier
|
77
119
|
begin
|
78
|
-
|
79
|
-
|
80
|
-
|
120
|
+
if notifier.nil? || (notifier.is_a?(Event) && ! notifier.set?)
|
121
|
+
result = act(*package.message)
|
122
|
+
end
|
81
123
|
rescue => ex
|
82
|
-
on_error(Time.now, message, ex)
|
124
|
+
on_error(Time.now, package.message, ex)
|
125
|
+
ensure
|
126
|
+
if package.handler.is_a?(Contract)
|
127
|
+
package.handler.complete(result, ex)
|
128
|
+
elsif notifier.is_a?(Event) && ! notifier.set?
|
129
|
+
package.handler.push(result || ex)
|
130
|
+
package.notifier.set
|
131
|
+
elsif package.handler.is_a?(Actor) && ex.nil?
|
132
|
+
package.handler.post(result)
|
133
|
+
end
|
134
|
+
|
135
|
+
changed
|
136
|
+
notify_observers(Time.now, package.message, result, ex)
|
83
137
|
end
|
84
138
|
end
|
85
139
|
|
data/lib/concurrent/agent.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'observer'
|
3
3
|
|
4
|
+
require 'concurrent/dereferenceable'
|
4
5
|
require 'concurrent/global_thread_pool'
|
5
6
|
require 'concurrent/utilities'
|
6
7
|
|
@@ -14,6 +15,7 @@ module Concurrent
|
|
14
15
|
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
15
16
|
class Agent
|
16
17
|
include Observable
|
18
|
+
include Dereferenceable
|
17
19
|
include UsesGlobalThreadPool
|
18
20
|
|
19
21
|
TIMEOUT = 5
|
@@ -25,29 +27,13 @@ module Concurrent
|
|
25
27
|
@value = initial
|
26
28
|
@rescuers = []
|
27
29
|
@validator = nil
|
28
|
-
|
29
30
|
@timeout = opts[:timeout] || TIMEOUT
|
30
|
-
|
31
|
-
@freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze] || false
|
32
|
-
@copy_on_deref = opts[:copy_on_deref] || opts[:copy]
|
33
|
-
|
34
|
-
@mutex = Mutex.new
|
35
|
-
end
|
36
|
-
|
37
|
-
def value(timeout = 0)
|
38
|
-
return @mutex.synchronize do
|
39
|
-
value = @value
|
40
|
-
value = @copy_on_deref.call(value) if @copy_on_deref
|
41
|
-
value = value.dup if @dup_on_deref
|
42
|
-
value = value.freeze if @freeze_on_deref
|
43
|
-
value
|
44
|
-
end
|
31
|
+
set_deref_options(opts)
|
45
32
|
end
|
46
|
-
alias_method :deref, :value
|
47
33
|
|
48
34
|
def rescue(clazz = nil, &block)
|
49
35
|
unless block.nil?
|
50
|
-
|
36
|
+
mutex.synchronize do
|
51
37
|
@rescuers << Rescuer.new(clazz, block)
|
52
38
|
end
|
53
39
|
end
|
@@ -82,7 +68,7 @@ module Concurrent
|
|
82
68
|
|
83
69
|
# @private
|
84
70
|
def try_rescue(ex) # :nodoc:
|
85
|
-
rescuer =
|
71
|
+
rescuer = mutex.synchronize do
|
86
72
|
@rescuers.find{|r| r.clazz.nil? || ex.is_a?(r.clazz) }
|
87
73
|
end
|
88
74
|
rescuer.block.call(ex) if rescuer
|
@@ -93,16 +79,16 @@ module Concurrent
|
|
93
79
|
# @private
|
94
80
|
def work(&handler) # :nodoc:
|
95
81
|
begin
|
96
|
-
|
82
|
+
mutex.synchronize do
|
97
83
|
result = Concurrent::timeout(@timeout) do
|
98
84
|
handler.call(@value)
|
99
85
|
end
|
100
86
|
if @validator.nil? || @validator.call(result)
|
101
87
|
@value = result
|
102
88
|
changed
|
103
|
-
notify_observers(Time.now, @value)
|
104
89
|
end
|
105
90
|
end
|
91
|
+
notify_observers(Time.now, self.value) if self.changed?
|
106
92
|
rescue Exception => ex
|
107
93
|
try_rescue(ex)
|
108
94
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'concurrent/obligation'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
|
5
|
+
class Contract
|
6
|
+
include Obligation
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
@state = :pending
|
10
|
+
set_deref_options(opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
def complete(value, reason)
|
14
|
+
@value = value
|
15
|
+
@reason = reason
|
16
|
+
@state = ( reason ? :rejected : :fulfilled )
|
17
|
+
event.set
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Concurrent
|
2
|
+
|
3
|
+
module Dereferenceable
|
4
|
+
|
5
|
+
def set_deref_options(opts = {})
|
6
|
+
mutex.synchronize do
|
7
|
+
@dup_on_deref = opts[:dup_on_deref] || opts[:dup] || false
|
8
|
+
@freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze] || false
|
9
|
+
@copy_on_deref = opts[:copy_on_deref] || opts[:copy]
|
10
|
+
@do_nothing_on_deref = ! (@dup_on_deref || @freeze_on_deref || @copy_on_deref)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def value
|
15
|
+
return nil if @value.nil?
|
16
|
+
return @value if @do_nothing_on_deref
|
17
|
+
return mutex.synchronize do
|
18
|
+
value = @value
|
19
|
+
value = @copy_on_deref.call(value) if @copy_on_deref
|
20
|
+
value = value.dup if @dup_on_deref
|
21
|
+
value = value.freeze if @freeze_on_deref
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
alias_method :deref, :value
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def mutex
|
30
|
+
@mutex ||= Mutex.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/concurrent/future.rb
CHANGED
@@ -12,7 +12,6 @@ module Concurrent
|
|
12
12
|
include UsesGlobalThreadPool
|
13
13
|
|
14
14
|
def initialize(*args, &block)
|
15
|
-
@mutex = Mutex.new
|
16
15
|
unless block_given?
|
17
16
|
@state = :fulfilled
|
18
17
|
else
|
@@ -25,9 +24,10 @@ module Concurrent
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def add_observer(observer, func = :update)
|
28
|
-
|
27
|
+
val = self.value
|
28
|
+
mutex.synchronize do
|
29
29
|
if event.set?
|
30
|
-
Future.thread_pool.post(func, Time.now,
|
30
|
+
Future.thread_pool.post(func, Time.now, val, @reason) do |f, *args|
|
31
31
|
observer.send(f, *args)
|
32
32
|
end
|
33
33
|
else
|
@@ -48,12 +48,13 @@ module Concurrent
|
|
48
48
|
@reason = ex
|
49
49
|
@state = :rejected
|
50
50
|
ensure
|
51
|
-
|
51
|
+
val = self.value
|
52
|
+
mutex.synchronize do
|
52
53
|
event.set
|
53
54
|
changed
|
54
|
-
notify_observers(Time.now,
|
55
|
+
notify_observers(Time.now, val, @reason)
|
55
56
|
delete_observers
|
56
|
-
|
57
|
+
end
|
57
58
|
end
|
58
59
|
end
|
59
60
|
end
|