concurrent-ruby 0.3.0 → 0.3.1.pre.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b89f9ac58f382096ac50f90e256d3cf68dd1ce3d
4
- data.tar.gz: 6bf065b9e23f67c79f9e7d3c524c6de780c78c38
3
+ metadata.gz: 86c7da12ad6b46036abb100e98a1450eda80fbb1
4
+ data.tar.gz: 43e1758062fd717a6e4a0c690890ef8149aa7b9e
5
5
  SHA512:
6
- metadata.gz: 2cadb5b8eb1e3ddebf91c375d6266a5f57e09b01d83222d2f06aee5312e286a9aa36d146aedf9d6acdf27d088821fc18a5e7fa14836e3645eebeacf8ba8ca095
7
- data.tar.gz: c2f0f34b9956391c4c63b3761de2bd871b3233b36a35d7ed16419b0c9b7385f4f1be884522cdd8c9d151cc86836bb1b3fec1872ca57410a12f4676f98c90399b
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](http://www.scala-lang.org/api/current/index.html#scala.actors.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
- * DelayedTask
222
- * More methods from Scala's Actor
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
@@ -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'
@@ -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
- # http://www.scala-lang.org/api/current/index.html#scala.actors.Actor
9
- class Actor
10
- include Observable
11
- include Runnable
10
+ module Postable
12
11
 
13
- def initialize
14
- @queue = Queue.new
15
- end
12
+ Package = Struct.new(:message, :handler, :notifier)
16
13
 
17
14
  def post(*message)
18
- return false unless running?
19
- @queue.push(message)
20
- return @queue.length
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
- @queue.clear
104
+ queue.clear
65
105
  end
66
106
 
67
107
  # @private
68
108
  def on_stop # :nodoc:
69
- @queue.clear
70
- @queue.push(:stop)
109
+ queue.clear
110
+ queue.push(:stop)
71
111
  end
72
112
 
73
113
  # @private
74
114
  def on_task # :nodoc:
75
- message = @queue.pop
76
- return if message == :stop
115
+ package = queue.pop
116
+ return if package == :stop
117
+ result = ex = nil
118
+ notifier = package.notifier
77
119
  begin
78
- result = act(*message)
79
- changed
80
- notify_observers(Time.now, message, result)
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
 
@@ -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
- @dup_on_deref = opts[:dup_on_deref] || opts[:dup] || false
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
- @mutex.synchronize do
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 = @mutex.synchronize do
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
- @mutex.synchronize do
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
@@ -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
- @mutex.synchronize do
27
+ val = self.value
28
+ mutex.synchronize do
29
29
  if event.set?
30
- Future.thread_pool.post(func, Time.now, @value, @reason) do |f, *args|
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
- @mutex.synchronize {
51
+ val = self.value
52
+ mutex.synchronize do
52
53
  event.set
53
54
  changed
54
- notify_observers(Time.now, @value, @reason)
55
+ notify_observers(Time.now, val, @reason)
55
56
  delete_observers
56
- }
57
+ end
57
58
  end
58
59
  end
59
60
  end