concurrent-ruby 0.4.1 → 0.5.0.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 +31 -33
- data/lib/concurrent.rb +11 -3
- data/lib/concurrent/actor.rb +29 -29
- data/lib/concurrent/agent.rb +98 -16
- data/lib/concurrent/atomic.rb +125 -0
- data/lib/concurrent/channel.rb +36 -1
- data/lib/concurrent/condition.rb +67 -0
- data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
- data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
- data/lib/concurrent/count_down_latch.rb +60 -0
- data/lib/concurrent/dataflow.rb +85 -0
- data/lib/concurrent/dereferenceable.rb +69 -31
- data/lib/concurrent/event.rb +27 -21
- data/lib/concurrent/future.rb +103 -43
- data/lib/concurrent/ivar.rb +78 -0
- data/lib/concurrent/mvar.rb +154 -0
- data/lib/concurrent/obligation.rb +94 -9
- data/lib/concurrent/postable.rb +11 -9
- data/lib/concurrent/promise.rb +101 -127
- data/lib/concurrent/safe_task_executor.rb +28 -0
- data/lib/concurrent/scheduled_task.rb +60 -54
- data/lib/concurrent/stoppable.rb +2 -2
- data/lib/concurrent/supervisor.rb +36 -29
- data/lib/concurrent/thread_local_var.rb +117 -0
- data/lib/concurrent/timer_task.rb +28 -30
- data/lib/concurrent/utilities.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/agent_spec.rb +121 -230
- data/spec/concurrent/atomic_spec.rb +201 -0
- data/spec/concurrent/condition_spec.rb +171 -0
- data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
- data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
- data/spec/concurrent/count_down_latch_spec.rb +125 -0
- data/spec/concurrent/dataflow_spec.rb +160 -0
- data/spec/concurrent/dereferenceable_shared.rb +145 -0
- data/spec/concurrent/event_spec.rb +44 -9
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
- data/spec/concurrent/future_spec.rb +184 -69
- data/spec/concurrent/ivar_spec.rb +192 -0
- data/spec/concurrent/mvar_spec.rb +380 -0
- data/spec/concurrent/obligation_spec.rb +193 -0
- data/spec/concurrent/observer_set_shared.rb +233 -0
- data/spec/concurrent/postable_shared.rb +3 -7
- data/spec/concurrent/promise_spec.rb +270 -192
- data/spec/concurrent/safe_task_executor_spec.rb +58 -0
- data/spec/concurrent/scheduled_task_spec.rb +142 -38
- data/spec/concurrent/thread_local_var_spec.rb +113 -0
- data/spec/concurrent/thread_pool_shared.rb +2 -3
- data/spec/concurrent/timer_task_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/functions.rb +4 -0
- data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
- metadata +50 -30
- data/lib/concurrent/contract.rb +0 -21
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
- data/md/actor.md +0 -404
- data/md/agent.md +0 -142
- data/md/channel.md +0 -40
- data/md/dereferenceable.md +0 -49
- data/md/future.md +0 -125
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -217
- data/md/scheduled_task.md +0 -156
- data/md/supervisor.md +0 -246
- data/md/thread_pool.md +0 -225
- data/md/timer_task.md +0 -191
- data/spec/concurrent/contract_spec.rb +0 -34
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75d1509eb5bc369de5272cdaf90447e357c45c6a
|
4
|
+
data.tar.gz: c2ae01438d9848893ab00a08880368bf82662f75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95702184ad897fd9874ae2f8bcaddcf0501a166f1dfad0fa37fc5be2a6c7b0d133bf74cd843753880135c3b4d8b63c8d515b08cf2b9d692fdfaf629533502fe2
|
7
|
+
data.tar.gz: dba5b747f74d44cf63475eb7a1dc081b3fea7fc7218ef016773c37a4f19db0af410ed6e8cb621d0dcdc363406ef3b63afe430bf8ae54916b2eab7374c7e887fc
|
data/README.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
# Concurrent Ruby
|
1
|
+
# Concurrent Ruby
|
2
|
+
[](http://badge.fury.io/rb/concurrent-ruby) [](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [](https://coveralls.io/r/jdantonio/concurrent-ruby) [](https://codeclimate.com/github/jdantonio/concurrent-ruby) [](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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
*
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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,
|
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).
|
data/lib/concurrent.rb
CHANGED
@@ -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
|
#
|
data/lib/concurrent/actor.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
34
|
-
# the
|
35
|
-
#
|
36
|
-
# which override it. Generally speaking,
|
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
|
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
|
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
|
-
#
|
46
|
-
# is added to an
|
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
|
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
|
61
|
-
# * Any exception raised by
|
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
|
144
|
-
# to manage all the messages being sent to an
|
145
|
-
# is a collection of
|
146
|
-
# Messages from other threads are all sent to a single queue against which all
|
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
|
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
|
195
|
-
# #act method. The #act method can have any signature/arity but
|
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
|
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
|
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
|
-
#
|
211
|
+
# @!visibility private
|
212
212
|
def on_run # :nodoc:
|
213
213
|
queue.clear
|
214
214
|
end
|
215
215
|
|
216
|
-
#
|
216
|
+
# @!visibility private
|
217
217
|
def on_stop # :nodoc:
|
218
218
|
queue.clear
|
219
219
|
queue.push(:stop)
|
220
220
|
end
|
221
221
|
|
222
|
-
#
|
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?(
|
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
|
-
#
|
249
|
+
# @!visibility private
|
250
250
|
def on_error(time, msg, ex) # :nodoc:
|
251
251
|
end
|
252
252
|
end
|
data/lib/concurrent/agent.rb
CHANGED
@@ -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 =
|
30
|
-
@timeout = opts
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
68
|
-
Rescuer = Struct.new(:clazz, :block)
|
145
|
+
# @!visibility private
|
146
|
+
Rescuer = Struct.new(:clazz, :block) # :nodoc:
|
69
147
|
|
70
|
-
#
|
148
|
+
# @!visibility private
|
71
149
|
def try_rescue(ex) # :nodoc:
|
72
150
|
rescuer = mutex.synchronize do
|
73
|
-
@rescuers.find{|r|
|
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
|
-
#
|
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.
|
168
|
+
if @validator.call(result)
|
88
169
|
@value = result
|
89
|
-
|
170
|
+
should_notify = true
|
90
171
|
end
|
91
172
|
end
|
92
|
-
|
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
|