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 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