concurrent-ruby 1.0.0.pre1 → 1.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/README.md +16 -18
  4. data/lib/concurrent.rb +3 -3
  5. data/lib/concurrent/agent.rb +583 -0
  6. data/lib/concurrent/array.rb +1 -0
  7. data/lib/concurrent/async.rb +236 -111
  8. data/lib/concurrent/atom.rb +101 -46
  9. data/lib/concurrent/atomic/atomic_boolean.rb +2 -0
  10. data/lib/concurrent/atomic/atomic_fixnum.rb +2 -0
  11. data/lib/concurrent/atomic/cyclic_barrier.rb +1 -1
  12. data/lib/concurrent/atomic/event.rb +1 -1
  13. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +1 -1
  14. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +1 -1
  15. data/lib/concurrent/atomic/mutex_count_down_latch.rb +1 -1
  16. data/lib/concurrent/atomic/mutex_semaphore.rb +2 -2
  17. data/lib/concurrent/atomic/read_write_lock.rb +5 -4
  18. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
  19. data/lib/concurrent/atomic/thread_local_var.rb +2 -0
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +1 -1
  21. data/lib/concurrent/atomics.rb +6 -4
  22. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +7 -15
  23. data/lib/concurrent/collection/copy_on_write_observer_set.rb +7 -15
  24. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +5 -0
  25. data/lib/concurrent/concern/observable.rb +38 -13
  26. data/lib/concurrent/configuration.rb +5 -4
  27. data/lib/concurrent/delay.rb +9 -8
  28. data/lib/concurrent/exchanger.rb +2 -0
  29. data/lib/concurrent/executor/abstract_executor_service.rb +2 -2
  30. data/lib/concurrent/executor/java_single_thread_executor.rb +0 -1
  31. data/lib/concurrent/executor/ruby_executor_service.rb +10 -4
  32. data/lib/concurrent/executor/ruby_single_thread_executor.rb +10 -68
  33. data/lib/concurrent/executor/safe_task_executor.rb +7 -8
  34. data/lib/concurrent/executor/serialized_execution.rb +4 -4
  35. data/lib/concurrent/executor/single_thread_executor.rb +20 -10
  36. data/lib/concurrent/executor/timer_set.rb +4 -2
  37. data/lib/concurrent/executors.rb +0 -1
  38. data/lib/concurrent/future.rb +3 -2
  39. data/lib/concurrent/hash.rb +1 -1
  40. data/lib/concurrent/immutable_struct.rb +5 -1
  41. data/lib/concurrent/ivar.rb +1 -1
  42. data/lib/concurrent/mutable_struct.rb +7 -6
  43. data/lib/concurrent/{executor/executor.rb → options.rb} +4 -3
  44. data/lib/concurrent/promise.rb +3 -2
  45. data/lib/concurrent/scheduled_task.rb +3 -2
  46. data/lib/concurrent/settable_struct.rb +5 -4
  47. data/lib/concurrent/synchronization.rb +11 -3
  48. data/lib/concurrent/synchronization/abstract_lockable_object.rb +117 -0
  49. data/lib/concurrent/synchronization/abstract_object.rb +16 -129
  50. data/lib/concurrent/synchronization/abstract_struct.rb +2 -3
  51. data/lib/concurrent/synchronization/condition.rb +6 -4
  52. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  53. data/lib/concurrent/synchronization/{java_object.rb → jruby_object.rb} +5 -3
  54. data/lib/concurrent/synchronization/lock.rb +3 -2
  55. data/lib/concurrent/synchronization/lockable_object.rb +59 -0
  56. data/lib/concurrent/synchronization/mri_lockable_object.rb +71 -0
  57. data/lib/concurrent/synchronization/mri_object.rb +35 -0
  58. data/lib/concurrent/synchronization/object.rb +111 -39
  59. data/lib/concurrent/synchronization/rbx_lockable_object.rb +64 -0
  60. data/lib/concurrent/synchronization/rbx_object.rb +17 -68
  61. data/lib/concurrent/thread_safe/util.rb +0 -9
  62. data/lib/concurrent/thread_safe/util/adder.rb +3 -0
  63. data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +3 -1
  64. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +3 -0
  65. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +1 -0
  66. data/lib/concurrent/thread_safe/util/striped64.rb +6 -1
  67. data/lib/concurrent/thread_safe/util/volatile.rb +2 -0
  68. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +2 -0
  69. data/lib/concurrent/tvar.rb +36 -0
  70. data/lib/concurrent/utility/at_exit.rb +1 -1
  71. data/lib/concurrent/utility/monotonic_time.rb +3 -4
  72. data/lib/concurrent/utility/native_extension_loader.rb +1 -1
  73. data/lib/concurrent/version.rb +2 -2
  74. metadata +12 -7
  75. data/lib/concurrent/synchronization/monitor_object.rb +0 -27
  76. data/lib/concurrent/synchronization/mutex_object.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73752ee1b21c6a7bed99d034af965e1f7bc12af7
4
- data.tar.gz: 1d1cfe49be663c6bef102761aae9dfa6e29e26e8
3
+ metadata.gz: 0148d111f8cfb5ce264c12f5a0cef91ca1bf36e3
4
+ data.tar.gz: 6d773f8549a2a1f989c5135d49e0a91ed51f04d6
5
5
  SHA512:
6
- metadata.gz: 0246cc3adb6b535e7d3149663e5ef771575b0c75d92509e8517853d13c1c06133b354573e5c900308bef5cab7f141dd06d0a89f79493841e6d1b19f6110088a3
7
- data.tar.gz: b5e8453b5123206bdaed3cc8122eb65077638a33b977f2d103be998ae42180cfe5bc23fc8eb921a9aab17208f4a26b4826211dd7e76b6275ba82ea705a49aec9
6
+ metadata.gz: 2bbee143971798fd8ea52f03fb361a9b5ce8fe3db4d5c8eecb45ee21b9ad29939dc458a9f20320496cc70f6fae22fe799d0b3d179e280b19180b28d9ba891d12
7
+ data.tar.gz: 45c8e415754d1148b839d28da1d386be611ba598b560d1c8e7acd7e7eb000621f0400b1901d94a1a10036836119f67c92b70a73484ae827adaecda74a496e108
@@ -1,4 +1,17 @@
1
- ## Current Release v1.0.0.pre1 (19 Aug 2015)
1
+ ## Current Release v1.0.0.pre2 (19 September 2015)
2
+
3
+ * Simplification of `RubySingleThreadExecutor`
4
+ * `Async` improvements
5
+ - Each object uses its own `SingleThreadExecutor` instead of the global thread pool.
6
+ - No longers supports executor injection
7
+ - Much better documentation
8
+ * `Atom` updates
9
+ - No longer `Dereferenceable`
10
+ - Now `Observable`
11
+ - Added a `#reset` method
12
+ * Brand new `Agent` API and implementation. Now functionally equivalent to Clojure.
13
+
14
+ ### Current Release v1.0.0.pre1 (19 August 2015)
2
15
 
3
16
  * Merged in the `thread_safe` gem
4
17
  - `Concurrent::Array`
data/README.md CHANGED
@@ -61,7 +61,7 @@ This library contains a variety of concurrency abstractions at high and low leve
61
61
 
62
62
  #### General-purpose Concurrency Abstractions
63
63
 
64
- * [Async](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Async.html): A mixin module that provides simple asynchronous behavior to any standard class/object or object.
64
+ * [Async](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Async.html): A mixin module that provides simple asynchronous behavior to a class. Loosely based on Erlang's [gen_server](http://www.erlang.org/doc/man/gen_server.html).
65
65
  * [Future](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Future.html): An asynchronous operation that produces a value.
66
66
  * [Dataflow](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#dataflow-class_method): Built on Futures, Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available.
67
67
  * [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features.
@@ -79,11 +79,9 @@ Collection classes that were originally part of the (deprecated) `thread_safe` g
79
79
 
80
80
  Value objects inspired by other languages:
81
81
 
82
- * [Atom](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Atom.html): A way to manage shared, synchronous, independent state.
83
82
  * [Maybe](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Maybe.html) A thread-safe, immutable object representing an optional value, based on
84
83
  [Haskell Data.Maybe](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html).
85
- * [Delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html) Lazy evaluation of a block yielding an immutable result. Based on Clojure's
86
- [delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html).
84
+ * [Delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html) Lazy evaluation of a block yielding an immutable result. Based on Clojure's [delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html).
87
85
 
88
86
  Structure classes derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/Struct.html):
89
87
 
@@ -93,10 +91,15 @@ Structure classes derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/St
93
91
 
94
92
  Thread-safe variables:
95
93
 
94
+ * [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A way to manage shared, mutable, *asynchronous*, independent, state. Based on Clojure's [Agent](http://clojure.org/agents).
95
+ * [Atom](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Atom.html): A way to manage shared, mutable, *synchronous*, independent state. Based on Clojure's [Atom](http://clojure.org/atoms).
96
96
  * [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicBoolean.html) A boolean value that can be updated atomically.
97
97
  * [AtomicFixnum](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicFixnum.html) A numeric value that can be updated atomically.
98
98
  * [AtomicReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MutexAtomic.html) An object reference that may be updated atomically.
99
+ * [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html) A synchronization point at which threads can pair and swap elements within pairs. Based on Java's [Exchanger](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html).
100
+ * [MVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) A synchronized single element container. Based on Haskell's [MVar](https://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Concurrent-MVar.html) and Scala's [MVar](http://docs.typelevel.org/api/scalaz/nightly/index.html#scalaz.concurrent.MVar$).
99
101
  * [ThreadLocalVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html) A variable where the value is different for each thread.
102
+ * [TVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) A transactional variable implementing software transactional memory (STM). Based on Clojure's [Ref](http://clojure.org/refs).
100
103
 
101
104
  #### Java-inspired ThreadPools and Other Executors
102
105
 
@@ -104,16 +107,13 @@ Thread-safe variables:
104
107
 
105
108
  #### Thread Synchronization Classes and Algorithms
106
109
 
107
- * [CountdownLatch](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CountDownLatch.html) A synchronization object that allows one thread to wait on multiple other threads.
110
+ * [CountDownLatch](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CountDownLatch.html) A synchronization object that allows one thread to wait on multiple other threads.
108
111
  * [CyclicBarrier](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CyclicBarrier.html) A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.
109
112
  * [Event](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Event.html) Old school kernel-style event.
110
- * [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html)
111
- * [I-Structure](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/IVar.html) (IVar) Similar to a "future" but can be manually assigned once, after which it becomes immutable.
112
- * [M-Structure](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) (MVar) A synchronized single element container.
113
+ * [IVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/IVar.html) Similar to a "future" but can be manually assigned once, after which it becomes immutable.
113
114
  * [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html) A lock that supports multiple readers but only one writer.
114
115
  * [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html) A read/write lock with reentrant and upgrade features.
115
116
  * [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html) A counting-based locking mechanism that uses permits.
116
- * [Software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar) A transactional variable - a single-element container that is used as part of a transaction.
117
117
 
118
118
  ### Edge Features
119
119
 
@@ -125,12 +125,11 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
125
125
 
126
126
  * [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html):
127
127
  Implements the Actor Model, where concurrent actors exchange messages.
128
- * [new Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/FutureShortcuts.html) - new
129
- unified implementation of Futures and Promises which combines Features of previous `Future`,
130
- `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
131
- new synchronization layer to make all the features **non-blocking** and **lock-free** with exception of obviously blocking
128
+ * [New Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/FutureShortcuts.html):
129
+ Unified implementation of futures and promises which combines features of previous `Future`,
130
+ `Promise`, `IVar`, `Event`, `dataflow`, `Delay`, and `TimerTask` into a single framework. It extensively uses the
131
+ new synchronization layer to make all the features **non-blocking** and **lock-free**, with the exception of obviously blocking
132
132
  operations like `#wait`, `#value`. It also offers better performance.
133
- * [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A single atomic value that represents an identity.
134
133
  * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html):
135
134
  Communicating Sequential Processes (CSP).
136
135
  * [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
@@ -142,12 +141,11 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
142
141
 
143
142
  *Why are these not in core?*
144
143
 
145
- - **Actor** - Partial documentation and tests; stability is good.
144
+ - **Actor** - Partial documentation and tests; depends on new future/promise framework; stability is good.
146
145
  - **Future/Promise Framework** - API changes; partial documentation and tests; stability good.
147
- - **Agent** - Incomplete behaviour compared to Clojure's models; stability good.
148
- - **Channel** - Missing documentation; limted features; stability good.
146
+ - **Channel** - Missing documentation; limited features; stability good.
149
147
  - **LazyRegister** - Missing documentation and tests.
150
- - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Needs real world battle testing
148
+ - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Need real world battle testing
151
149
 
152
150
  ## Usage
153
151
 
@@ -1,14 +1,13 @@
1
1
  require 'concurrent/version'
2
-
3
- require 'concurrent/synchronization'
4
-
5
2
  require 'concurrent/configuration'
6
3
 
7
4
  require 'concurrent/atomics'
8
5
  require 'concurrent/errors'
9
6
  require 'concurrent/executors'
7
+ require 'concurrent/synchronization'
10
8
 
11
9
  require 'concurrent/atomic/atomic_reference'
10
+ require 'concurrent/agent'
12
11
  require 'concurrent/atom'
13
12
  require 'concurrent/array'
14
13
  require 'concurrent/hash'
@@ -33,6 +32,7 @@ require 'concurrent/tvar'
33
32
  require 'concurrent/thread_safe/synchronized_delegator'
34
33
  require 'concurrent/thread_safe/util'
35
34
 
35
+ require 'concurrent/options'
36
36
 
37
37
  # @!macro [new] internal_implementation_note
38
38
  #
@@ -0,0 +1,583 @@
1
+ require 'concurrent/configuration'
2
+ require 'concurrent/atomic/atomic_reference'
3
+ require 'concurrent/atomic/thread_local_var'
4
+ require 'concurrent/collection/copy_on_write_observer_set'
5
+ require 'concurrent/concern/observable'
6
+ require 'concurrent/synchronization'
7
+
8
+ module Concurrent
9
+
10
+ # `Agent` is inspired by Clojure's [agent](http://clojure.org/agents)
11
+ # function. An agent is a shared, mutable variable providing independent,
12
+ # uncoordinated, *asynchronous* change of individual values. Best used when
13
+ # the value will undergo frequent, complex updates. Suitable when the result
14
+ # of an update does not need to be known immediately. `Agent` is (mostly)
15
+ # functionally equivalent to Clojure's agent, except where the runtime
16
+ # prevents parity.
17
+ #
18
+ # Agents are reactive, not autonomous - there is no imperative message loop
19
+ # and no blocking receive. The state of an Agent should be itself immutable
20
+ # and the `#value` of an Agent is always immediately available for reading by
21
+ # any thread without any messages, i.e. observation does not require
22
+ # cooperation or coordination.
23
+ #
24
+ # Agent action dispatches are made using the various `#send` methods. These
25
+ # methods always return immediately. At some point later, in another thread,
26
+ # the following will happen:
27
+ #
28
+ # 1. The given `action` will be applied to the state of the Agent and the
29
+ # `args`, if any were supplied.
30
+ # 2. The return value of `action` will be passed to the validator lambda,
31
+ # if one has been set on the Agent.
32
+ # 3. If the validator succeeds or if no validator was given, the return value
33
+ # of the given `action` will become the new `#value` of the Agent. See
34
+ # `#initialize` for details.
35
+ # 4. If any observers were added to the Agent, they will be notified. See
36
+ # `#add_observer` for details.
37
+ # 5. If during the `action` execution any other dispatches are made (directly
38
+ # or indirectly), they will be held until after the `#value` of the Agent
39
+ # has been changed.
40
+ #
41
+ # If any exceptions are thrown by an action function, no nested dispatches
42
+ # will occur, and the exception will be cached in the Agent itself. When an
43
+ # Agent has errors cached, any subsequent interactions will immediately throw
44
+ # an exception, until the agent's errors are cleared. Agent errors can be
45
+ # examined with `#error` and the agent restarted with `#restart`.
46
+ #
47
+ # The actions of all Agents get interleaved amongst threads in a thread pool.
48
+ # At any point in time, at most one action for each Agent is being executed.
49
+ # Actions dispatched to an agent from another single agent or thread will
50
+ # occur in the order they were sent, potentially interleaved with actions
51
+ # dispatched to the same agent from other sources. The `#send` method should
52
+ # be used for actions that are CPU limited, while the `#send_off` method is
53
+ # appropriate for actions that may block on IO.
54
+ #
55
+ # Unlike in Clojure, `Agent` cannot participate in `Concurrent::TVar` transactions.
56
+ #
57
+ # ## Example
58
+ #
59
+ # ```
60
+ # def next_fibonacci(set = nil)
61
+ # return [0, 1] if set.nil?
62
+ # set + [set[-2..-1].reduce{|sum,x| sum + x }]
63
+ # end
64
+ #
65
+ # # create an agent with an initial value
66
+ # agent = Concurrent::Agent.new(next_fibonacci)
67
+ #
68
+ # # send a few update requests
69
+ # 5.times do
70
+ # agent.send{|set| next_fibonacci(set) }
71
+ # end
72
+ #
73
+ # # wait for them to complete
74
+ # agent.await
75
+ #
76
+ # # get the current value
77
+ # agent.value #=> [0, 1, 1, 2, 3, 5, 8]
78
+ # ```
79
+ #
80
+ # ## Observation
81
+ #
82
+ # Agents support observers through the {Concurrent::Observable} mixin module.
83
+ # Notification of observers occurs every time an action dispatch returns and
84
+ # the new value is successfully validated. Observation will *not* occur if the
85
+ # action raises an exception, if validation fails, or when a {#restart} occurs.
86
+ #
87
+ # When notified the observer will receive three arguments: `time`, `old_value`,
88
+ # and `new_value`. The `time` argument is the time at which the value change
89
+ # occurred. The `old_value` is the value of the Agent when the action began
90
+ # processing. The `new_value` is the value to which the Agent was set when the
91
+ # action completed. Note that `old_value` and `new_value` may be the same.
92
+ # This is not an error. It simply means that the action returned the same
93
+ # value.
94
+ #
95
+ # ## Nested Actions
96
+ #
97
+ # It is possible for an Agent action to post further actions back to itself.
98
+ # The nested actions will be enqueued normally then processed *after* the
99
+ # outer action completes, in the order they were sent, possibly interleaved
100
+ # with action dispatches from other threads. Nested actions never deadlock
101
+ # with one another and a failure in a nested action will never affect the
102
+ # outer action.
103
+ #
104
+ # Nested actions can be called using the Agent reference from the enclosing
105
+ # scope or by passing the reference in as a "send" argument. Nested actions
106
+ # cannot be post using `self` from within the action block/proc/lambda; `self`
107
+ # in this context will not reference the Agent. The preferred method for
108
+ # dispatching nested actions is to pass the Agent as an argument. This allows
109
+ # Ruby to more effectively manage the closing scope.
110
+ #
111
+ # Prefer this:
112
+ #
113
+ # ```
114
+ # agent = Concurrent::Agent.new(0)
115
+ # agent.send(agent) do |value, this|
116
+ # this.send {|v| v + 42 }
117
+ # 3.14
118
+ # end
119
+ # agent.value #=> 45.14
120
+ # ```
121
+ #
122
+ # Over this:
123
+ #
124
+ # ```
125
+ # agent = Concurrent::Agent.new(0)
126
+ # agent.send do |value|
127
+ # agent.send {|v| v + 42 }
128
+ # 3.14
129
+ # end
130
+ # ```
131
+ #
132
+ # @!macro [new] agent_await_warning
133
+ #
134
+ # **NOTE** Never, *under any circumstances*, call any of the "await" methods
135
+ # ({#await}, {#await_for}, {#await_for!}, and {#wait}) from within an action
136
+ # block/proc/lambda. The call will block the Agent and will always fail.
137
+ # Calling either {#await} or {#wait} (with a timeout of `nil`) will
138
+ # hopelessly deadlock the Agent with no possibility of recovery.
139
+ #
140
+ # @!macro thread_safe_variable_comparison
141
+ #
142
+ # @see http://clojure.org/Agents Clojure Agents
143
+ # @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State
144
+ class Agent < Synchronization::LockableObject
145
+ include Concern::Observable
146
+
147
+ ERROR_MODES = [:continue, :fail].freeze
148
+ private_constant :ERROR_MODES
149
+
150
+ AWAIT_FLAG = Object.new
151
+ private_constant :AWAIT_FLAG
152
+
153
+ AWAIT_ACTION = ->(value, latch) { latch.count_down; AWAIT_FLAG }
154
+ private_constant :AWAIT_ACTION
155
+
156
+ DEFAULT_ERROR_HANDLER = ->(agent, error) { nil }
157
+ private_constant :DEFAULT_ERROR_HANDLER
158
+
159
+ DEFAULT_VALIDATOR = ->(value) { true }
160
+ private_constant :DEFAULT_VALIDATOR
161
+
162
+ Job = Struct.new(:action, :args, :executor, :caller)
163
+ private_constant :Job
164
+
165
+ # Raised during action processing or any other time in an Agent's lifecycle.
166
+ class Error < StandardError
167
+ def initialize(message = nil)
168
+ message ||= 'agent must be restarted before jobs can post'
169
+ end
170
+ end
171
+
172
+ # Raised when a new value obtained during action processing or at `#restart`
173
+ # fails validation.
174
+ class ValidationError < Error
175
+ def initialize(message = nil)
176
+ message ||= 'invalid value'
177
+ end
178
+ end
179
+
180
+ # The error mode this Agent is operating in. See {#initialize} for details.
181
+ attr_reader :error_mode
182
+
183
+ # Create a new `Agent` with the given initial value and options.
184
+ #
185
+ # The `:validator` option must be `nil` or a side-effect free proc/lambda
186
+ # which takes one argument. On any intended value change the validator, if
187
+ # provided, will be called. If the new value is invalid the validator should
188
+ # return `false` or raise an error.
189
+ #
190
+ # The `:error_handler` option must be `nil` or a proc/lambda which takes two
191
+ # arguments. When an action raises an error or validation fails, either by
192
+ # returning false or raising an error, the error handler will be called. The
193
+ # arguments to the error handler will be a reference to the agent itself and
194
+ # the error object which was raised.
195
+ #
196
+ # The `:error_mode` may be either `:continue` (the default if an error
197
+ # handler is given) or `:fail` (the default if error handler nil or not
198
+ # given).
199
+ #
200
+ # If an action being run by the agent throws an error or doesn't pass
201
+ # validation the error handler, if present, will be called. After the
202
+ # handler executes if the error mode is `:continue` the Agent will continue
203
+ # as if neither the action that caused the error nor the error itself ever
204
+ # happened.
205
+ #
206
+ # If the mode is `:fail` the Agent will become {#failed?} and will stop
207
+ # accepting new action dispatches. Any previously queued actions will be
208
+ # held until {#restart} is called. The {#value} method will still work,
209
+ # returning the value of the Agent before the error.
210
+ #
211
+ # @param [Object] initial the initial value
212
+ # @param [Hash] opts the configuration options
213
+ #
214
+ # @option opts [Symbol] :error_mode either `:continue` or `:fail`
215
+ # @option opts [nil, Proc] :error_handler the (optional) error handler
216
+ # @option opts [nil, Proc] :validator the (optional) validation procedure
217
+ def initialize(initial, opts = {})
218
+ super()
219
+ synchronize { ns_initialize(initial, opts) }
220
+ end
221
+
222
+ # The current value (state) of the Agent, irrespective of any pending or
223
+ # in-progress actions. The value is always available and is non-blocking.
224
+ #
225
+ # @return [Object] the current value
226
+ def value
227
+ @current.value # TODO (pitr 12-Sep-2015): broken unsafe read?
228
+ end
229
+
230
+ alias_method :deref, :value
231
+
232
+ # When {#failed?} and {#error_mode} is `:fail`, returns the error object
233
+ # which caused the failure, else `nil`. When {#error_mode} is `:continue`
234
+ # will *always* return `nil`.
235
+ #
236
+ # @return [nil, Error] the error which caused the failure when {#failed?}
237
+ def error
238
+ @error.value
239
+ end
240
+
241
+ alias_method :reason, :error
242
+
243
+ # @!macro [attach] agent_send
244
+ #
245
+ # Dispatches an action to the Agent and returns immediately. Subsequently,
246
+ # in a thread from a thread pool, the {#value} will be set to the return
247
+ # value of the action. Action dispatches are only allowed when the Agent
248
+ # is not {#failed?}.
249
+ #
250
+ # The action must be a block/proc/lambda which takes 1 or more arguments.
251
+ # The first argument is the current {#value} of the Agent. Any arguments
252
+ # passed to the send method via the `args` parameter will be passed to the
253
+ # action as the remaining arguments. The action must return the new value
254
+ # of the Agent.
255
+ #
256
+ # * {#send} and {#send!} should be used for actions that are CPU limited
257
+ # * {#send_off}, {#send_off!}, and {#<<} are appropriate for actions that
258
+ # may block on IO
259
+ # * {#send_via} and {#send_via!} are used when a specific executor is to
260
+ # be used for the action
261
+ #
262
+ # @param [Array<Object>] args zero or more arguments to be passed to
263
+ # the action
264
+ # @param [Proc] action the action dispatch to be enqueued
265
+ #
266
+ # @yield [agent, value, *args] process the old value and return the new
267
+ # @yieldparam [Object] value the current {#value} of the Agent
268
+ # @yieldparam [Array<Object>] args zero or more arguments to pass to the
269
+ # action
270
+ # @yieldreturn [Object] the new value of the Agent
271
+ #
272
+ # @!macro [attach] send_return
273
+ # @return [Boolean] true if the action is successfully enqueued, false if
274
+ # the Agent is {#failed?}
275
+ def send(*args, &action)
276
+ enqueue_action_job(action, args, Concurrent.global_fast_executor)
277
+ end
278
+
279
+ # @!macro agent_send
280
+ #
281
+ # @!macro [attach] send_bang_return_and_raise
282
+ # @return [Boolean] true if the action is successfully enqueued
283
+ # @raise [Concurrent::Agent::Error] if the Agent is {#failed?}
284
+ def send!(*args, &action)
285
+ raise Error.new unless send(*args, &action)
286
+ true
287
+ end
288
+
289
+ # @!macro agent_send
290
+ # @!macro send_return
291
+ def send_off(*args, &action)
292
+ enqueue_action_job(action, args, Concurrent.global_io_executor)
293
+ end
294
+
295
+ alias_method :post, :send_off
296
+
297
+ # @!macro agent_send
298
+ # @!macro send_bang_return_and_raise
299
+ def send_off!(*args, &action)
300
+ raise Error.new unless send_off(*args, &action)
301
+ true
302
+ end
303
+
304
+ # @!macro agent_send
305
+ # @!macro send_return
306
+ # @param [Concurrent::ExecutorService] executor the executor on which the
307
+ # action is to be dispatched
308
+ def send_via(executor, *args, &action)
309
+ enqueue_action_job(action, args, executor)
310
+ end
311
+
312
+ # @!macro agent_send
313
+ # @!macro send_bang_return_and_raise
314
+ # @param [Concurrent::ExecutorService] executor the executor on which the
315
+ # action is to be dispatched
316
+ def send_via!(executor, *args, &action)
317
+ raise Error.new unless send_via(executor, *args, &action)
318
+ true
319
+ end
320
+
321
+ # Dispatches an action to the Agent and returns immediately. Subsequently,
322
+ # in a thread from a thread pool, the {#value} will be set to the return
323
+ # value of the action. Appropriate for actions that may block on IO.
324
+ #
325
+ # @param [Proc] action the action dispatch to be enqueued
326
+ # @return [Concurrent::Agent] self
327
+ # @see {#send_off}
328
+ def <<(action)
329
+ send_off(&action)
330
+ self
331
+ end
332
+
333
+ # Blocks the current thread (indefinitely!) until all actions dispatched
334
+ # thus far, from this thread or nested by the Agent, have occurred. Will
335
+ # block when {#failed?}. Will never return if a failed Agent is {#restart}
336
+ # with `:clear_actions` true.
337
+ #
338
+ # Returns a reference to `self` to support method chaining:
339
+ #
340
+ # ```
341
+ # current_value = agent.await.value
342
+ # ```
343
+ #
344
+ # @return [Boolean] self
345
+ #
346
+ # @!macro agent_await_warning
347
+ def await
348
+ wait(nil)
349
+ self
350
+ end
351
+
352
+ # Blocks the current thread until all actions dispatched thus far, from this
353
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
354
+ # has elapsed.
355
+ #
356
+ # @param [Float] timeout the maximum number of seconds to wait
357
+ # @return [Boolean] true if all actions complete before timeout else false
358
+ #
359
+ # @!macro agent_await_warning
360
+ def await_for(timeout)
361
+ wait(timeout.to_f)
362
+ end
363
+
364
+ # Blocks the current thread until all actions dispatched thus far, from this
365
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
366
+ # has elapsed.
367
+ #
368
+ # @param [Float] timeout the maximum number of seconds to wait
369
+ # @return [Boolean] true if all actions complete before timeout
370
+ #
371
+ # @raise [Concurrent::TimeoutError] when timout is reached
372
+ #
373
+ # @!macro agent_await_warning
374
+ def await_for!(timeout)
375
+ raise Concurrent::TimeoutError unless wait(timeout.to_f)
376
+ true
377
+ end
378
+
379
+ # Blocks the current thread until all actions dispatched thus far, from this
380
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
381
+ # has elapsed. Will block indefinitely when timeout is nil or not given.
382
+ #
383
+ # Provided mainly for consistency with other classes in this library. Prefer
384
+ # the various `await` methods instead.
385
+ #
386
+ # @param [Float] timeout the maximum number of seconds to wait
387
+ # @return [Boolean] true if all actions complete before timeout else false
388
+ #
389
+ # @!macro agent_await_warning
390
+ def wait(timeout = nil)
391
+ latch = Concurrent::CountDownLatch.new(1)
392
+ enqueue_await_job(latch)
393
+ latch.wait(timeout)
394
+ end
395
+
396
+ # Is the Agent in a failed state?
397
+ #
398
+ # @see {#restart}
399
+ def failed?
400
+ !@error.value.nil?
401
+ end
402
+
403
+ alias_method :stopped?, :failed?
404
+
405
+ # When an Agent is {#failed?}, changes the Agent {#value} to `new_value`
406
+ # then un-fails the Agent so that action dispatches are allowed again. If
407
+ # the `:clear_actions` option is give and true, any actions queued on the
408
+ # Agent that were being held while it was failed will be discarded,
409
+ # otherwise those held actions will proceed. The `new_value` must pass the
410
+ # validator if any, or `restart` will raise an exception and the Agent will
411
+ # remain failed with its old {#value} and {#error}. Observers, if any, will
412
+ # not be notified of the new state.
413
+ #
414
+ # @param [Object] new_value the new value for the Agent once restarted
415
+ # @param [Hash] opts the configuration options
416
+ # @option opts [Symbol] :clear_actions true if all enqueued but unprocessed
417
+ # actions should be discarded on restart, else false (default: false)
418
+ # @return [Boolean] true
419
+ #
420
+ # @raise [Concurrent:AgentError] when not failed
421
+ def restart(new_value, opts = {})
422
+ clear_actions = opts.fetch(:clear_actions, false)
423
+ synchronize do
424
+ raise Error.new('agent is not failed') unless failed?
425
+ raise ValidationError unless ns_validate(new_value)
426
+ @current.value = new_value
427
+ @error.value = nil
428
+ @queue.clear if clear_actions
429
+ ns_post_next_job unless @queue.empty?
430
+ end
431
+ true
432
+ end
433
+
434
+ class << self
435
+
436
+ # Blocks the current thread (indefinitely!) until all actions dispatched
437
+ # thus far to all the given Agents, from this thread or nested by the
438
+ # given Agents, have occurred. Will block when any of the agents are
439
+ # failed. Will never return if a failed Agent is restart with
440
+ # `:clear_actions` true.
441
+ #
442
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
443
+ # @return [Boolean] true
444
+ #
445
+ # @!macro agent_await_warning
446
+ def await(*agents)
447
+ agents.each { |agent| agent.await }
448
+ true
449
+ end
450
+
451
+ # Blocks the current thread until all actions dispatched thus far to all
452
+ # the given Agents, from this thread or nested by the given Agents, have
453
+ # occurred, or the timeout (in seconds) has elapsed.
454
+ #
455
+ # @param [Float] timeout the maximum number of seconds to wait
456
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
457
+ # @return [Boolean] true if all actions complete before timeout else false
458
+ #
459
+ # @!macro agent_await_warning
460
+ def await_for(timeout, *agents)
461
+ end_at = Concurrent.monotonic_time + timeout.to_f
462
+ ok = agents.length.times do |i|
463
+ break false if (delay = end_at - Concurrent.monotonic_time) < 0
464
+ break false unless agents[i].await_for(delay)
465
+ end
466
+ !!ok
467
+ end
468
+
469
+ # Blocks the current thread until all actions dispatched thus far to all
470
+ # the given Agents, from this thread or nested by the given Agents, have
471
+ # occurred, or the timeout (in seconds) has elapsed.
472
+ #
473
+ # @param [Float] timeout the maximum number of seconds to wait
474
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
475
+ # @return [Boolean] true if all actions complete before timeout
476
+ #
477
+ # @raise [Concurrent::TimeoutError] when timout is reached
478
+ # @!macro agent_await_warning
479
+ def await_for!(timeout, *agents)
480
+ raise Concurrent::TimeoutError unless await_for(timeout, *agents)
481
+ true
482
+ end
483
+ end
484
+
485
+ private
486
+
487
+ def ns_initialize(initial, opts)
488
+ @error_mode = opts[:error_mode]
489
+ @error_handler = opts[:error_handler]
490
+
491
+ if @error_mode && !ERROR_MODES.include?(@error_mode)
492
+ raise ArgumentError.new('unrecognized error mode')
493
+ elsif @error_mode.nil?
494
+ @error_mode = @error_handler ? :continue : :fail
495
+ end
496
+
497
+ @error_handler ||= DEFAULT_ERROR_HANDLER
498
+ @validator = opts.fetch(:validator, DEFAULT_VALIDATOR)
499
+ @current = Concurrent::AtomicReference.new(initial)
500
+ @error = Concurrent::AtomicReference.new(nil)
501
+ @caller = Concurrent::ThreadLocalVar.new(nil)
502
+ @queue = []
503
+
504
+ self.observers = Collection::CopyOnNotifyObserverSet.new
505
+ end
506
+
507
+ def enqueue_action_job(action, args, executor)
508
+ raise ArgumentError.new('no action given') unless action
509
+ job = Job.new(action, args, executor, @caller.value || Thread.current.object_id)
510
+ synchronize { ns_enqueue_job(job) }
511
+ end
512
+
513
+ def enqueue_await_job(latch)
514
+ synchronize do
515
+ if (index = ns_find_last_job_for_thread)
516
+ job = Job.new(AWAIT_ACTION, [latch], Concurrent.global_immediate_executor,
517
+ Thread.current.object_id)
518
+ ns_enqueue_job(job, index+1)
519
+ else
520
+ latch.count_down
521
+ true
522
+ end
523
+ end
524
+ end
525
+
526
+ def ns_enqueue_job(job, index = nil)
527
+ # a non-nil index means this is an await job
528
+ return false if index.nil? && failed?
529
+ index ||= @queue.length
530
+ @queue.insert(index, job)
531
+ # if this is the only job, post to executor
532
+ ns_post_next_job if @queue.length == 1
533
+ true
534
+ end
535
+
536
+ def ns_post_next_job
537
+ @queue.first.executor.post { execute_next_job }
538
+ end
539
+
540
+ def execute_next_job
541
+ job = synchronize { @queue.first }
542
+ old_value = @current.value
543
+
544
+ @caller.value = job.caller # for nested actions
545
+ new_value = job.action.call(old_value, *job.args)
546
+ @caller.value = nil
547
+
548
+ if new_value != AWAIT_FLAG && ns_validate(new_value)
549
+ @current.value = new_value
550
+ observers.notify_observers(Time.now, old_value, new_value)
551
+ else
552
+ handle_error(ValidationError.new)
553
+ end
554
+ rescue => error
555
+ handle_error(error)
556
+ ensure
557
+ synchronize do
558
+ @queue.shift
559
+ unless failed? || @queue.empty?
560
+ ns_post_next_job
561
+ end
562
+ end
563
+ end
564
+
565
+ def ns_validate(value)
566
+ @validator.call(value)
567
+ rescue
568
+ false
569
+ end
570
+
571
+ def handle_error(error)
572
+ # stop new jobs from posting
573
+ @error.value = error if @error_mode == :fail
574
+ @error_handler.call(self, error)
575
+ rescue
576
+ # do nothing
577
+ end
578
+
579
+ def ns_find_last_job_for_thread
580
+ @queue.rindex { |job| job.caller == Thread.current.object_id }
581
+ end
582
+ end
583
+ end