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.
- 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
|
+
[![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
|
-
|
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
|