concurrent-ruby 0.4.1 → 0.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -33
  3. data/lib/concurrent.rb +11 -3
  4. data/lib/concurrent/actor.rb +29 -29
  5. data/lib/concurrent/agent.rb +98 -16
  6. data/lib/concurrent/atomic.rb +125 -0
  7. data/lib/concurrent/channel.rb +36 -1
  8. data/lib/concurrent/condition.rb +67 -0
  9. data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
  10. data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
  11. data/lib/concurrent/count_down_latch.rb +60 -0
  12. data/lib/concurrent/dataflow.rb +85 -0
  13. data/lib/concurrent/dereferenceable.rb +69 -31
  14. data/lib/concurrent/event.rb +27 -21
  15. data/lib/concurrent/future.rb +103 -43
  16. data/lib/concurrent/ivar.rb +78 -0
  17. data/lib/concurrent/mvar.rb +154 -0
  18. data/lib/concurrent/obligation.rb +94 -9
  19. data/lib/concurrent/postable.rb +11 -9
  20. data/lib/concurrent/promise.rb +101 -127
  21. data/lib/concurrent/safe_task_executor.rb +28 -0
  22. data/lib/concurrent/scheduled_task.rb +60 -54
  23. data/lib/concurrent/stoppable.rb +2 -2
  24. data/lib/concurrent/supervisor.rb +36 -29
  25. data/lib/concurrent/thread_local_var.rb +117 -0
  26. data/lib/concurrent/timer_task.rb +28 -30
  27. data/lib/concurrent/utilities.rb +1 -1
  28. data/lib/concurrent/version.rb +1 -1
  29. data/spec/concurrent/agent_spec.rb +121 -230
  30. data/spec/concurrent/atomic_spec.rb +201 -0
  31. data/spec/concurrent/condition_spec.rb +171 -0
  32. data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
  33. data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
  34. data/spec/concurrent/count_down_latch_spec.rb +125 -0
  35. data/spec/concurrent/dataflow_spec.rb +160 -0
  36. data/spec/concurrent/dereferenceable_shared.rb +145 -0
  37. data/spec/concurrent/event_spec.rb +44 -9
  38. data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
  39. data/spec/concurrent/future_spec.rb +184 -69
  40. data/spec/concurrent/ivar_spec.rb +192 -0
  41. data/spec/concurrent/mvar_spec.rb +380 -0
  42. data/spec/concurrent/obligation_spec.rb +193 -0
  43. data/spec/concurrent/observer_set_shared.rb +233 -0
  44. data/spec/concurrent/postable_shared.rb +3 -7
  45. data/spec/concurrent/promise_spec.rb +270 -192
  46. data/spec/concurrent/safe_task_executor_spec.rb +58 -0
  47. data/spec/concurrent/scheduled_task_spec.rb +142 -38
  48. data/spec/concurrent/thread_local_var_spec.rb +113 -0
  49. data/spec/concurrent/thread_pool_shared.rb +2 -3
  50. data/spec/concurrent/timer_task_spec.rb +31 -1
  51. data/spec/spec_helper.rb +2 -3
  52. data/spec/support/functions.rb +4 -0
  53. data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
  54. metadata +50 -30
  55. data/lib/concurrent/contract.rb +0 -21
  56. data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
  57. data/md/actor.md +0 -404
  58. data/md/agent.md +0 -142
  59. data/md/channel.md +0 -40
  60. data/md/dereferenceable.md +0 -49
  61. data/md/future.md +0 -125
  62. data/md/obligation.md +0 -32
  63. data/md/promise.md +0 -217
  64. data/md/scheduled_task.md +0 -156
  65. data/md/supervisor.md +0 -246
  66. data/md/thread_pool.md +0 -225
  67. data/md/timer_task.md +0 -191
  68. data/spec/concurrent/contract_spec.rb +0 -34
  69. data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a2299d64fb78b008f9f686ac6f72b87e92f54b5
4
- data.tar.gz: b76068bdde155ed1e3f0572dca60b34cb9e7f952
3
+ metadata.gz: 75d1509eb5bc369de5272cdaf90447e357c45c6a
4
+ data.tar.gz: c2ae01438d9848893ab00a08880368bf82662f75
5
5
  SHA512:
6
- metadata.gz: cc3346484a3fce841b6e10ee5406bb938a8d16f48295b08fd71014187cf7adb24c398b6856aef6ad62ef74e6d13d5a4a5adc9b1a57102b5175eaf269256850fd
7
- data.tar.gz: 8c30bbcfde894063c3c466dd7a66b13c30827674d13446dc0a9f95b46509c326913ca927303047bf7537971a49905c0286cd90a4e45788e89504277034ea5834
6
+ metadata.gz: 95702184ad897fd9874ae2f8bcaddcf0501a166f1dfad0fa37fc5be2a6c7b0d133bf74cd843753880135c3b4d8b63c8d515b08cf2b9d692fdfaf629533502fe2
7
+ data.tar.gz: dba5b747f74d44cf63475eb7a1dc081b3fea7fc7218ef016773c37a4f19db0af410ed6e8cb621d0dcdc363406ef3b63afe430bf8ae54916b2eab7374c7e887fc
data/README.md CHANGED
@@ -1,4 +1,7 @@
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)
1
+ # Concurrent Ruby
2
+ [![Gem Version](https://badge.fury.io/rb/concurrent-ruby.png)](http://badge.fury.io/rb/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) [![Code Climate](https://codeclimate.com/github/jdantonio/concurrent-ruby.png)](https://codeclimate.com/github/jdantonio/concurrent-ruby) [![Dependency Status](https://gemnasium.com/jdantonio/concurrent-ruby.png)](https://gemnasium.com/jdantonio/concurrent-ruby)
3
+
4
+ ***NOTE:*** *A few API updates in v0.5.0 are not backward-compatible. Please see the [release notes](https://github.com/jdantonio/concurrent-ruby/wiki/API-Updates-in-v0.5.0).*
2
5
 
3
6
  Modern concurrency tools for Ruby. Inspired by
4
7
  [Erlang](http://www.erlang.org/doc/reference_manual/processes.html),
@@ -21,26 +24,36 @@ The design goals of this gem are:
21
24
 
22
25
  ## Features & Documentation
23
26
 
24
- * Clojure-inspired [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
25
- * Clojure-inspired [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
26
- * Scala-inspired [Actor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/actor.md)
27
- * JavaScript-inspired [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
28
- * Java-inspired [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
29
- * Old school [events](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx) from back in my Visual C++ days
30
- * Repeated task execution with Java-inspired [TimerTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/timer_task.md) service
31
- * Scheduled task execution with Java-inspired [ScheduledTask](https://github.com/jdantonio/concurrent-ruby/blob/master/md/scheduled_task.md) service
32
- * Erlang-inspired [Supervisor](https://github.com/jdantonio/concurrent-ruby/blob/master/md/supervisor.md) for managing long-running threads
33
- * Actor variant [Channel](https://github.com/jdantonio/concurrent-ruby/blob/master/md/channel.md)
34
- loosely based on the [MailboxProcessor](http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx)
35
- agent in [F#](http://msdn.microsoft.com/en-us/library/ee370357.aspx)
27
+ Please see the [Concurrent Ruby Wiki](https://github.com/jdantonio/concurrent-ruby/wiki)
28
+ or the [API documentation](http://rubydoc.info/github/jdantonio/concurrent-ruby/master/frames)
29
+ for more information or join our [mailing list](http://groups.google.com/group/concurrent-ruby).
30
+
31
+ There are many concurrency abstractions in this library. These abstractions can be broadly categorized
32
+ into several general categories:
33
+
34
+ * Asynchronous concurrency abstractions including [Actor](https://github.com/jdantonio/concurrent-ruby/wiki/Actor),
35
+ [Agent](https://github.com/jdantonio/concurrent-ruby/wiki/Agent), [Channel](https://github.com/jdantonio/concurrent-ruby/wiki/Channel),
36
+ [Future](https://github.com/jdantonio/concurrent-ruby/wiki/Future), [Promise](https://github.com/jdantonio/concurrent-ruby/wiki/Promise),
37
+ [ScheculedTask](https://github.com/jdantonio/concurrent-ruby/wiki/ScheduledTask),
38
+ and [TimerTask](https://github.com/jdantonio/concurrent-ruby/wiki/TimerTask)
39
+ * Erlang-inspired [Supervisor](https://github.com/jdantonio/concurrent-ruby/wiki/Supervisor) and other lifecycle classes/mixins
40
+ for managing long-running threads
41
+ * Thread-safe variables including [M-Structures](https://github.com/jdantonio/concurrent-ruby/wiki/MVar-(M-Structure)),
42
+ [I-Structures](https://github.com/jdantonio/concurrent-ruby/wiki/IVar-(I-Structure)),
43
+ [thread-local variables](https://github.com/jdantonio/concurrent-ruby/wiki/ThreadLocalVar),
44
+ and atomic counters
45
+ * Thread synchronization classes and algorithms including [dataflow](https://github.com/jdantonio/concurrent-ruby/wiki/Dataflow),
46
+ timeout, condition, countdown latch, dependency counter, and event
47
+ * Java-inspired [thread pools](https://github.com/jdantonio/concurrent-ruby/wiki/Thread%20Pools)
48
+ * And many more...
36
49
 
37
50
  ### Semantic Versioning
38
51
 
39
- *Beginning with v0.4.0 this gem will strictly adhere to the rules of semantic versioning.*
52
+ This gem adheres to the rules of [semantic versioning](http://semver.org/).
40
53
 
41
54
  ### Supported Ruby versions
42
55
 
43
- MRI 1.9.2, 1.9.3, 2.0, 2.1, and JRuby (1.9 mode). This library is pure Ruby and has no gem dependencies.
56
+ MRI 1.9.2, 1.9.3, 2.0, 2.1, JRuby (1.9 mode), and Rubinius 2.x. This library is pure Ruby and has no gem dependencies.
44
57
  It should be fully compatible with any Ruby interpreter that is 1.9.x compliant.
45
58
 
46
59
  ### Example
@@ -106,6 +119,9 @@ These tools will help ease the burden, but at the end of the day it is essential
106
119
  ## Contributors
107
120
 
108
121
  * [Michele Della Torre](https://github.com/mighe)
122
+ * [Chris Seaton](https://github.com/chrisseaton)
123
+ * [Giuseppe Capizzi](https://github.com/gcapizzi)
124
+ * [Brian Shirai](https://github.com/brixen)
109
125
  * [Chip Miller](https://github.com/chip-miller)
110
126
  * [Jamie Hodge](https://github.com/jamiehodge)
111
127
  * [Zander Hill](https://github.com/zph)
@@ -118,24 +134,6 @@ These tools will help ease the burden, but at the end of the day it is essential
118
134
  4. Push to the branch (`git push origin my-new-feature`)
119
135
  5. Create new Pull Request
120
136
 
121
- ### Conference Presentations
122
-
123
- I've given several conference presentations on concurrent programming with this gem.
124
- Check them out:
125
-
126
- * ["Advanced Concurrent Programming in Ruby"](http://rubyconf.org/program#jerry-dantonio)
127
- at [RubyConf 2013](http://rubyconf.org/)
128
- used [this](https://github.com/jdantonio/concurrent-ruby-presentation) version of the presentation
129
- and is available for viewing on [Confreaks](http://www.confreaks.com/videos/2872-rubyconf2013-advanced-concurrent-programming-in-ruby)
130
- * ["Advanced Multithreading in Ruby"](http://cascadiaruby.com/#advanced-multithreading-in-ruby)
131
- at [Cascadia Ruby 2013](http://cascadiaruby.com/)
132
- used [this](https://github.com/jdantonio/concurrent-ruby-presentation/tree/cascadia-ruby-2013) version of the presentation
133
- and is available for viewing on [Confreaks](http://www.confreaks.com/videos/2790-cascadiaruby2013-advanced-multithreading-in-ruby)
134
- * [Cleveland Ruby Brigade](http://www.meetup.com/ClevelandRuby/events/149981942/) meetup in December of 2013
135
- used [this](https://github.com/jdantonio/concurrent-ruby-presentation/releases/tag/clerb-dec-2013) version of the presentation
136
- * I'll be giving ["Advanced Concurrent Programming in Ruby"](http://codemash.org/sessions)
137
- at [CodeMash 2014](http://codemash.org/)
138
-
139
137
  ## License and Copyright
140
138
 
141
139
  *Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
@@ -1,12 +1,21 @@
1
1
  require 'concurrent/version'
2
2
 
3
+ require 'concurrent/atomic'
4
+ require 'concurrent/count_down_latch'
5
+ require 'concurrent/condition'
6
+ require 'concurrent/copy_on_notify_observer_set'
7
+ require 'concurrent/copy_on_write_observer_set'
8
+ require 'concurrent/safe_task_executor'
9
+ require 'concurrent/ivar'
10
+
3
11
  require 'concurrent/actor'
4
12
  require 'concurrent/agent'
5
- require 'concurrent/contract'
6
13
  require 'concurrent/channel'
14
+ require 'concurrent/dataflow'
7
15
  require 'concurrent/dereferenceable'
8
16
  require 'concurrent/event'
9
17
  require 'concurrent/future'
18
+ require 'concurrent/mvar'
10
19
  require 'concurrent/obligation'
11
20
  require 'concurrent/postable'
12
21
  require 'concurrent/promise'
@@ -14,6 +23,7 @@ require 'concurrent/runnable'
14
23
  require 'concurrent/scheduled_task'
15
24
  require 'concurrent/stoppable'
16
25
  require 'concurrent/supervisor'
26
+ require 'concurrent/thread_local_var'
17
27
  require 'concurrent/timer_task'
18
28
  require 'concurrent/utilities'
19
29
 
@@ -23,8 +33,6 @@ require 'concurrent/cached_thread_pool'
23
33
  require 'concurrent/fixed_thread_pool'
24
34
  require 'concurrent/immediate_executor'
25
35
 
26
- require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
27
-
28
36
  # Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,
29
37
  # F#, C#, Java, and classic concurrency patterns.
30
38
  #
@@ -19,36 +19,36 @@ module Concurrent
19
19
  #
20
20
  # An independent, concurrent, single-purpose, computational entity that communicates exclusively via message passing.
21
21
  #
22
- # The `Concurrent::Actor` class in this library is based solely on the
22
+ # The +Concurrent::Actor+ class in this library is based solely on the
23
23
  # {http://www.scala-lang.org/api/current/index.html#scala.actors.Actor Actor} trait
24
24
  # defined in the Scala standard library. It does not implement all the features of
25
- # Scala's `Actor` but its behavior for what *has* been implemented is nearly identical.
25
+ # Scala's +Actor+ but its behavior for what *has* been implemented is nearly identical.
26
26
  # The excluded features mostly deal with Scala's message semantics, strong typing,
27
27
  # and other characteristics of Scala that don't really apply to Ruby.
28
28
  #
29
- # Unlike most of the abstractions in this library, `Actor` takes an *object-oriented*
29
+ # Unlike many of the abstractions in this library, +Actor+ takes an *object-oriented*
30
30
  # approach to asynchronous concurrency, rather than a *functional programming*
31
31
  # approach.
32
32
  #
33
- # Because `Actor` mixes in the `Concurrent::Runnable` module subclasses have access to
34
- # the `#on_error` method and can override it to implement custom error handling. The
35
- # `Actor` base class does not use `#on_error` so as to avoid conflit with subclasses
36
- # which override it. Generally speaking, `#on_error` should not be used. The `Actor`
33
+ # Because +Actor+ mixes in the +Concurrent::Runnable+ module subclasses have access to
34
+ # the +#on_error+ method and can override it to implement custom error handling. The
35
+ # +Actor+ base class does not use +#on_error+ so as to avoid conflit with subclasses
36
+ # which override it. Generally speaking, +#on_error+ should not be used. The +Actor+
37
37
  # base class provides concictent, reliable, and robust error handling already, and
38
38
  # error handling specifics are tied to the message posting method. Incorrect behavior
39
- # in an `#on_error` override can lead to inconsistent `Actor` behavior that may lead
39
+ # in an +#on_error+ override can lead to inconsistent +Actor+ behavior that may lead
40
40
  # to confusion and difficult debugging.
41
41
  #
42
- # The `Actor` superclass mixes in the Ruby standard library
42
+ # The +Actor+ superclass mixes in the Ruby standard library
43
43
  # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html Observable}
44
44
  # module to provide consistent callbacks upon message processing completion. The normal
45
- # `Observable` methods, including `#add_observer` behave normally. Once an observer
46
- # is added to an `Actor` it will be notified of all messages processed *after*
45
+ # +Observable+ methods, including +#add_observer+ behave normally. Once an observer
46
+ # is added to an +Actor+ it will be notified of all messages processed *after*
47
47
  # addition. Notification will *not* occur for any messages that have already been
48
48
  # processed.
49
49
  #
50
50
  # Observers will be notified regardless of whether the message processing is successful
51
- # or not. The `#update` method of the observer will receive four arguments. The
51
+ # or not. The +#update+ method of the observer will receive four arguments. The
52
52
  # appropriate method signature is:
53
53
  #
54
54
  # def update(time, message, result, reason)
@@ -57,8 +57,8 @@ module Concurrent
57
57
  #
58
58
  # * The time that message processing was completed
59
59
  # * An array containing all elements of the original message, in order
60
- # * The result of the call to `#act` (will be `nil` if an exception was raised)
61
- # * Any exception raised by `#act` (or `nil` if message processing was successful)
60
+ # * The result of the call to +#act+ (will be +nil+ if an exception was raised)
61
+ # * Any exception raised by +#act+ (or +nil+ if message processing was successful)
62
62
  #
63
63
  # @example Actor Ping Pong
64
64
  # class Ping < Concurrent::Actor
@@ -140,10 +140,10 @@ module Concurrent
140
140
 
141
141
  # Create a pool of actors that share a common mailbox.
142
142
  #
143
- # Every `Actor` instance operates on its own thread. When one thread isn't enough capacity
144
- # to manage all the messages being sent to an `Actor` a *pool* can be used instead. A pool
145
- # is a collection of `Actor` instances, all of the same type, that shate a message queue.
146
- # Messages from other threads are all sent to a single queue against which all `Actor`s
143
+ # Every +Actor+ instance operates on its own thread. When one thread isn't enough capacity
144
+ # to manage all the messages being sent to an +Actor+ a *pool* can be used instead. A pool
145
+ # is a collection of +Actor+ instances, all of the same type, that shate a message queue.
146
+ # Messages from other threads are all sent to a single queue against which all +Actor+s
147
147
  # load balance.
148
148
  #
149
149
  # @param [Integer] count the number of actors in the pool
@@ -152,7 +152,7 @@ module Concurrent
152
152
  # @return [Array] two-element array with the shared mailbox as the first element
153
153
  # and an array of actors as the second element
154
154
  #
155
- # @raise ArgumentError if `count` is zero or less
155
+ # @raise ArgumentError if +count+ is zero or less
156
156
  #
157
157
  # @example
158
158
  # class EchoActor < Concurrent::Actor
@@ -191,35 +191,35 @@ module Concurrent
191
191
 
192
192
  protected
193
193
 
194
- # Actors are defined by subclassing the `Concurrent::Actor` class and overriding the
195
- # #act method. The #act method can have any signature/arity but `def act(*args)`
194
+ # Actors are defined by subclassing the +Concurrent::Actor+ class and overriding the
195
+ # #act method. The #act method can have any signature/arity but +def act(*args)+
196
196
  # is the most flexible and least error-prone signature. The #act method is called in
197
- # response to a message being post to the `Actor` instance (see *Behavior* below).
197
+ # response to a message being post to the +Actor+ instance (see *Behavior* below).
198
198
  #
199
199
  # @param [Array] message one or more arguments representing the message sent to the
200
200
  # actor via one of the Concurrent::Postable methods
201
201
  #
202
202
  # @return [Object] the result obtained when the message is successfully processed
203
203
  #
204
- # @raise NotImplementedError unless overridden in the `Actor` subclass
204
+ # @raise NotImplementedError unless overridden in the +Actor+ subclass
205
205
  #
206
206
  # @!visibility public
207
207
  def act(*message)
208
208
  raise NotImplementedError.new("#{self.class} does not implement #act")
209
209
  end
210
210
 
211
- # @private
211
+ # @!visibility private
212
212
  def on_run # :nodoc:
213
213
  queue.clear
214
214
  end
215
215
 
216
- # @private
216
+ # @!visibility private
217
217
  def on_stop # :nodoc:
218
218
  queue.clear
219
219
  queue.push(:stop)
220
220
  end
221
221
 
222
- # @private
222
+ # @!visibility private
223
223
  def on_task # :nodoc:
224
224
  package = queue.pop
225
225
  return if package == :stop
@@ -235,8 +235,8 @@ module Concurrent
235
235
  if notifier.is_a?(Event) && ! notifier.set?
236
236
  package.handler.push(result || ex)
237
237
  package.notifier.set
238
- elsif package.handler.is_a?(Contract)
239
- package.handler.complete(result, ex)
238
+ elsif package.handler.is_a?(IVar)
239
+ package.handler.complete(! result.nil?, result, ex)
240
240
  elsif package.handler.respond_to?(:post) && ex.nil?
241
241
  package.handler.post(result)
242
242
  end
@@ -246,7 +246,7 @@ module Concurrent
246
246
  end
247
247
  end
248
248
 
249
- # @private
249
+ # @!visibility private
250
250
  def on_error(time, msg, ex) # :nodoc:
251
251
  end
252
252
  end
@@ -13,83 +13,165 @@ module Concurrent
13
13
  # will receive the current value of the agent as its sole parameter. The return value of the block
14
14
  # will become the new value of the agent. Agents support two error handling modes: fail and continue.
15
15
  # A good example of an agent is a shared incrementing counter, such as the score in a video game.
16
+ #
17
+ # @example Basic usage
18
+ # score = Concurrent::Agent.new(10)
19
+ # score.value #=> 10
20
+ #
21
+ # score << proc{|current| current + 100 }
22
+ # sleep(0.1)
23
+ # score.value #=> 110
24
+ #
25
+ # score << proc{|current| current * 2 }
26
+ # sleep(0.1)
27
+ # score.value #=> 220
28
+ #
29
+ # score << proc{|current| current - 50 }
30
+ # sleep(0.1)
31
+ # score.value #=> 170
32
+ #
33
+ # @!attribute [r] timeout
34
+ # @return [Fixnum] the maximum number of seconds before an update is cancelled
16
35
  class Agent
17
- include Observable
18
36
  include Dereferenceable
19
37
  include UsesGlobalThreadPool
20
38
 
39
+ # The default timeout value (in seconds); used when no timeout option
40
+ # is given at initialization
21
41
  TIMEOUT = 5
22
42
 
23
- attr_reader :initial
24
43
  attr_reader :timeout
25
44
 
45
+ # Initialize a new Agent with the given initial value and provided options.
46
+ #
47
+ # @param [Object] initial the initial value
48
+ # @param [Hash] opts the options used to define the behavior at update and deref
49
+ # @option opts [Fixnum] :timeout (TIMEOUT) maximum number of seconds before an update is cancelled
50
+ # @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
51
+ # @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
52
+ # @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
53
+ # returning the value returned from the proc
26
54
  def initialize(initial, opts = {})
27
55
  @value = initial
28
56
  @rescuers = []
29
- @validator = nil
30
- @timeout = opts[:timeout] || TIMEOUT
57
+ @validator = Proc.new { |result| true }
58
+ @timeout = opts.fetch(:timeout, TIMEOUT).freeze
59
+ @observers = CopyOnWriteObserverSet.new
31
60
  init_mutex
32
61
  set_deref_options(opts)
33
62
  end
34
63
 
35
- def rescue(clazz = nil, &block)
64
+ # Specifies a block operation to be performed when an update operation raises
65
+ # an exception. Rescue blocks will be checked in order they were added. The first
66
+ # block for which the raised exception "is-a" subclass of the given +clazz+ will
67
+ # be called. If no +clazz+ is given the block will match any caught exception.
68
+ # This behavior is intended to be identical to Ruby's +begin/rescue/end+ behavior.
69
+ # Any number of rescue handlers can be added. If no rescue handlers are added then
70
+ # caught exceptions will be suppressed.
71
+ #
72
+ # @param [Exception] clazz the class of exception to catch
73
+ # @yield the block to be called when a matching exception is caught
74
+ # @yieldparam [StandardError] ex the caught exception
75
+ #
76
+ # @example
77
+ # score = Concurrent::Agent.new(0).
78
+ # rescue(NoMethodError){|ex| puts "Bam!" }.
79
+ # rescue(ArgumentError){|ex| puts "Pow!" }.
80
+ # rescue{|ex| puts "Boom!" }
81
+ #
82
+ # score << proc{|current| raise ArgumentError }
83
+ # sleep(0.1)
84
+ # #=> puts "Pow!"
85
+ def rescue(clazz = StandardError, &block)
36
86
  unless block.nil?
37
87
  mutex.synchronize do
38
88
  @rescuers << Rescuer.new(clazz, block)
39
89
  end
40
90
  end
41
- return self
91
+ self
42
92
  end
43
93
  alias_method :catch, :rescue
44
94
  alias_method :on_error, :rescue
45
95
 
96
+ # A block operation to be performed after every update to validate if the new
97
+ # value is valid. If the new value is not valid then the current value is not
98
+ # updated. If no validator is provided then all updates are considered valid.
99
+ #
100
+ # @yield the block to be called after every update operation to determine if
101
+ # the result is valid
102
+ # @yieldparam [Object] value the result of the last update operation
103
+ # @yieldreturn [Boolean] true if the value is valid else false
46
104
  def validate(&block)
47
105
  @validator = block unless block.nil?
48
- return self
106
+ self
49
107
  end
50
108
  alias_method :validates, :validate
51
109
  alias_method :validate_with, :validate
52
110
  alias_method :validates_with, :validate
53
111
 
112
+ # Update the current value with the result of the given block operation
113
+ #
114
+ # @yield the operation to be performed with the current value in order to calculate
115
+ # the new value
116
+ # @yieldparam [Object] value the current value
117
+ # @yieldreturn [Object] the new value
54
118
  def post(&block)
55
119
  Agent.thread_pool.post{ work(&block) } unless block.nil?
56
120
  end
57
121
 
122
+ # Update the current value with the result of the given block operation
123
+ #
124
+ # @yield the operation to be performed with the current value in order to calculate
125
+ # the new value
126
+ # @yieldparam [Object] value the current value
127
+ # @yieldreturn [Object] the new value
58
128
  def <<(block)
59
129
  self.post(&block)
60
- return self
130
+ self
131
+ end
132
+
133
+ def add_observer(observer, func=:update)
134
+ @observers.add_observer(observer, func)
61
135
  end
62
136
 
63
137
  alias_method :add_watch, :add_observer
64
138
 
139
+ def delete_observer(observer)
140
+ @observers.delete_observer(observer)
141
+ end
142
+
65
143
  private
66
144
 
67
- # @private
68
- Rescuer = Struct.new(:clazz, :block)
145
+ # @!visibility private
146
+ Rescuer = Struct.new(:clazz, :block) # :nodoc:
69
147
 
70
- # @private
148
+ # @!visibility private
71
149
  def try_rescue(ex) # :nodoc:
72
150
  rescuer = mutex.synchronize do
73
- @rescuers.find{|r| r.clazz.nil? || ex.is_a?(r.clazz) }
151
+ @rescuers.find{|r| ex.is_a?(r.clazz) }
74
152
  end
75
153
  rescuer.block.call(ex) if rescuer
76
154
  rescue Exception => ex
77
155
  # supress
78
156
  end
79
157
 
80
- # @private
158
+ # @!visibility private
81
159
  def work(&handler) # :nodoc:
82
160
  begin
161
+
162
+ should_notify = false
163
+
83
164
  mutex.synchronize do
84
165
  result = Concurrent::timeout(@timeout) do
85
166
  handler.call(@value)
86
167
  end
87
- if @validator.nil? || @validator.call(result)
168
+ if @validator.call(result)
88
169
  @value = result
89
- changed
170
+ should_notify = true
90
171
  end
91
172
  end
92
- notify_observers(Time.now, self.value) if self.changed?
173
+ time = Time.now
174
+ @observers.notify_observers{ [time, self.value] } if should_notify
93
175
  rescue Exception => ex
94
176
  try_rescue(ex)
95
177
  end