concurrent-ruby 0.9.2-java → 1.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/README.md +86 -120
  4. data/lib/concurrent.rb +14 -5
  5. data/lib/concurrent/agent.rb +587 -0
  6. data/lib/concurrent/array.rb +39 -0
  7. data/lib/concurrent/async.rb +296 -149
  8. data/lib/concurrent/atom.rb +135 -45
  9. data/lib/concurrent/atomic/abstract_thread_local_var.rb +38 -0
  10. data/lib/concurrent/atomic/atomic_boolean.rb +83 -118
  11. data/lib/concurrent/atomic/atomic_fixnum.rb +101 -163
  12. data/lib/concurrent/atomic/atomic_reference.rb +1 -8
  13. data/lib/concurrent/atomic/count_down_latch.rb +62 -103
  14. data/lib/concurrent/atomic/cyclic_barrier.rb +3 -1
  15. data/lib/concurrent/atomic/event.rb +1 -1
  16. data/lib/concurrent/atomic/java_count_down_latch.rb +39 -0
  17. data/lib/concurrent/atomic/java_thread_local_var.rb +50 -0
  18. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +60 -0
  19. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +91 -0
  20. data/lib/concurrent/atomic/mutex_count_down_latch.rb +43 -0
  21. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  22. data/lib/concurrent/atomic/read_write_lock.rb +5 -4
  23. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
  24. data/lib/concurrent/atomic/ruby_thread_local_var.rb +172 -0
  25. data/lib/concurrent/atomic/semaphore.rb +84 -178
  26. data/lib/concurrent/atomic/thread_local_var.rb +65 -294
  27. data/lib/concurrent/atomic_reference/jruby+truffle.rb +1 -0
  28. data/lib/concurrent/atomic_reference/jruby.rb +1 -1
  29. data/lib/concurrent/atomic_reference/mutex_atomic.rb +14 -8
  30. data/lib/concurrent/atomic_reference/ruby.rb +1 -1
  31. data/lib/concurrent/atomics.rb +7 -37
  32. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +7 -15
  33. data/lib/concurrent/collection/copy_on_write_observer_set.rb +7 -15
  34. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  35. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  36. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  37. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +144 -0
  38. data/lib/concurrent/collection/map/synchronized_map_backend.rb +86 -0
  39. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  40. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  41. data/lib/concurrent/concern/dereferenceable.rb +9 -24
  42. data/lib/concurrent/concern/logging.rb +1 -1
  43. data/lib/concurrent/concern/obligation.rb +11 -20
  44. data/lib/concurrent/concern/observable.rb +38 -13
  45. data/lib/concurrent/configuration.rb +23 -152
  46. data/lib/concurrent/constants.rb +8 -0
  47. data/lib/concurrent/delay.rb +14 -12
  48. data/lib/concurrent/exchanger.rb +339 -41
  49. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  50. data/lib/concurrent/executor/executor_service.rb +23 -359
  51. data/lib/concurrent/executor/immediate_executor.rb +3 -2
  52. data/lib/concurrent/executor/java_executor_service.rb +100 -0
  53. data/lib/concurrent/executor/java_single_thread_executor.rb +3 -3
  54. data/lib/concurrent/executor/java_thread_pool_executor.rb +3 -4
  55. data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
  56. data/lib/concurrent/executor/ruby_single_thread_executor.rb +10 -66
  57. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +25 -22
  58. data/lib/concurrent/executor/safe_task_executor.rb +6 -7
  59. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  60. data/lib/concurrent/executor/serialized_execution.rb +10 -33
  61. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  62. data/lib/concurrent/executor/simple_executor_service.rb +1 -10
  63. data/lib/concurrent/executor/single_thread_executor.rb +20 -10
  64. data/lib/concurrent/executor/timer_set.rb +8 -10
  65. data/lib/concurrent/executors.rb +12 -2
  66. data/lib/concurrent/future.rb +6 -4
  67. data/lib/concurrent/hash.rb +35 -0
  68. data/lib/concurrent/immutable_struct.rb +5 -1
  69. data/lib/concurrent/ivar.rb +12 -16
  70. data/lib/concurrent/lazy_register.rb +11 -8
  71. data/lib/concurrent/map.rb +180 -0
  72. data/lib/concurrent/maybe.rb +6 -3
  73. data/lib/concurrent/mutable_struct.rb +7 -6
  74. data/lib/concurrent/mvar.rb +26 -2
  75. data/lib/concurrent/{executor/executor.rb → options.rb} +5 -29
  76. data/lib/concurrent/promise.rb +7 -5
  77. data/lib/concurrent/scheduled_task.rb +13 -71
  78. data/lib/concurrent/settable_struct.rb +5 -4
  79. data/lib/concurrent/synchronization.rb +15 -3
  80. data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  81. data/lib/concurrent/synchronization/abstract_object.rb +7 -146
  82. data/lib/concurrent/synchronization/abstract_struct.rb +2 -3
  83. data/lib/concurrent/synchronization/condition.rb +6 -4
  84. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  85. data/lib/concurrent/synchronization/jruby_object.rb +44 -0
  86. data/lib/concurrent/synchronization/lock.rb +3 -2
  87. data/lib/concurrent/synchronization/lockable_object.rb +72 -0
  88. data/lib/concurrent/synchronization/mri_lockable_object.rb +71 -0
  89. data/lib/concurrent/synchronization/mri_object.rb +43 -0
  90. data/lib/concurrent/synchronization/object.rb +140 -73
  91. data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
  92. data/lib/concurrent/synchronization/rbx_object.rb +30 -73
  93. data/lib/concurrent/synchronization/volatile.rb +34 -0
  94. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  95. data/lib/concurrent/thread_safe/util.rb +14 -0
  96. data/lib/concurrent/thread_safe/util/adder.rb +74 -0
  97. data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +30 -0
  98. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  99. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  100. data/lib/concurrent/thread_safe/util/striped64.rb +241 -0
  101. data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
  102. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  103. data/lib/concurrent/timer_task.rb +3 -4
  104. data/lib/concurrent/tuple.rb +86 -0
  105. data/lib/concurrent/tvar.rb +5 -1
  106. data/lib/concurrent/utility/at_exit.rb +1 -1
  107. data/lib/concurrent/utility/engine.rb +4 -0
  108. data/lib/concurrent/utility/monotonic_time.rb +3 -4
  109. data/lib/concurrent/utility/native_extension_loader.rb +50 -30
  110. data/lib/concurrent/version.rb +2 -2
  111. data/lib/concurrent_ruby_ext.jar +0 -0
  112. metadata +47 -12
  113. data/lib/concurrent/atomic/condition.rb +0 -78
  114. data/lib/concurrent/collection/priority_queue.rb +0 -360
  115. data/lib/concurrent/synchronization/java_object.rb +0 -34
  116. data/lib/concurrent/synchronization/monitor_object.rb +0 -27
  117. data/lib/concurrent/synchronization/mutex_object.rb +0 -43
  118. data/lib/concurrent/utilities.rb +0 -5
  119. data/lib/concurrent/utility/timeout.rb +0 -39
  120. data/lib/concurrent/utility/timer.rb +0 -26
  121. data/lib/concurrent_ruby.rb +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58fcb6fe96e8a611c32e4d23d3016f11786496f7
4
- data.tar.gz: bed6902157997d4f55097c9bf4829e3e75a38622
3
+ metadata.gz: 495623f62c0b2d65273bf9bbd7ed0d27b0a0f25e
4
+ data.tar.gz: bcb17e5681ab7d8b718ec7baa49fe69d2796ac87
5
5
  SHA512:
6
- metadata.gz: 83c7e538c3bafbd71ae156d393110ab3387dd83b56a0d6a1f06588da2e430e41bd3896f1d50f1d3b047c3523a7f4985d4a72a60e6bf54d1bc011fadc9c3a7ce2
7
- data.tar.gz: f589e034b4bc9138b9c44c2f76ffeaccef3d01299e93ede9866f6f972b4aa3153b4f56fa8b8aa076b5084cecaff858423d8f1bb712dc117f4c38b481e5af59b3
6
+ metadata.gz: ba74c5a7a368e70a8b5fa5238a0bdc0d0d13825bc6e6c3d38b6ce64c8a1558f526812148cc823f26e563b1bcc02ae6d4a58f4bb717498b77db65876698e0e9dd
7
+ data.tar.gz: 39ca74c4ff022cb1c1b306e418b9e56bbb4df31b258d64b6793ad99a18b8b71fe13b0f6862b6153331b2ed8c84e86284d52a37de0bcdd137a4f21c20551bef5d
@@ -1,4 +1,52 @@
1
- ## Current Release v0.9.1 (09 August 2015)
1
+ ## Current Release v1.0.0 (13 November 2015)
2
+
3
+ * Rename `attr_volatile_with_cas` to `attr_atomic`
4
+ * Add `clear_each` to `LockFreeStack`
5
+ * Update `AtomicReference` documentation
6
+ * Further updates and improvements to the synchronization layer.
7
+ * Performance and memory usage performance with `Actor` logging.
8
+ * Fixed `ThreadPoolExecutor` task count methods.
9
+ * Improved `Async` performance for both short and long-lived objects.
10
+ * Fixed bug in `LockFreeLinkedSet`.
11
+ * Fixed bug in which `Agent#await` triggered a validation failure.
12
+ * Further `Channel` updates.
13
+ * Adopted a project Code of Conduct
14
+ * Cleared interpreter warnings
15
+ * Fixed bug in `ThreadPoolExecutor` task count methods
16
+ * Fixed bug in 'LockFreeLinkedSet'
17
+ * Improved Java extension loading
18
+ * Handle Exception children in Edge::Future
19
+ * Continued improvements to channel
20
+ * Removed interpreter warnings.
21
+ * Shared constants now in `lib/concurrent/constants.rb`
22
+ * Refactored many tests.
23
+ * Improved synchronization layer/memory model documentation.
24
+ * Bug fix in Edge `Future#flat`
25
+ * Brand new `Channel` implementation in Edge gem.
26
+ * Simplification of `RubySingleThreadExecutor`
27
+ * `Async` improvements
28
+ - Each object uses its own `SingleThreadExecutor` instead of the global thread pool.
29
+ - No longers supports executor injection
30
+ - Much better documentation
31
+ * `Atom` updates
32
+ - No longer `Dereferenceable`
33
+ - Now `Observable`
34
+ - Added a `#reset` method
35
+ * Brand new `Agent` API and implementation. Now functionally equivalent to Clojure.
36
+ * Continued improvements to the synchronization layer
37
+ * Merged in the `thread_safe` gem
38
+ - `Concurrent::Array`
39
+ - `Concurrent::Hash`
40
+ - `Concurrent::Map` (formerly ThreadSafe::Cache)
41
+ - `Concurrent::Tuple`
42
+ * Minor improvements to Concurrent::Map
43
+ * Complete rewrite of `Exchanger`
44
+ * Removed all deprecated code (classes, methods, constants, etc.)
45
+ * Updated Agent, MutexAtomic, and BufferedChannel to inherit from Synchronization::Object.
46
+ * Many improved tests
47
+ * Some internal reorganization
48
+
49
+ ### Release v0.9.1 (09 August 2015)
2
50
 
3
51
  * Fixed a Rubiniux bug in synchronization object
4
52
  * Fixed all interpreter warnings (except circular references)
data/README.md CHANGED
@@ -47,7 +47,15 @@
47
47
 
48
48
  MRI 1.9.3, 2.0, 2.1, 2.2, JRuby (1.9 mode), and Rubinius 2.x are supported.
49
49
  This gem should be fully compatible with any interpreter that is compliant with Ruby 1.9.3 or newer.
50
- Java 8 is required for JRuby (Java 7 support is deprecated in version 0.9 and will be removed in 1.0).
50
+ Java 8 is preferred for JRuby but every Java version on which JRuby 9000 runs will be supported.
51
+
52
+ ## Thread Safety
53
+
54
+ *Concurrent Ruby makes the strongest thread safety guarantees of any Ruby concurrency library. We are the only library with a published [memory model](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/doc/synchronization.md) which provides consistent behavior and guarantees on all three of the main Ruby interpreters (MRI/CRuby, JRuby, and Rubinius).*
55
+
56
+ Every abstraction in this library is thread safe. Similarly, all are deadlock free and many are fully lock free. Specific thread safety guarantees are documented with each abstraction.
57
+
58
+ It is critical to remember, however, that Ruby is a language of mutable references. *No* concurrency library for Ruby can ever prevent the user from making thread safety mistakes (such as sharing a mutable object between threads and modifying it on both threads) or from creating deadlocks through incorrect use of locks. All the library can do is provide safe abstractions which encourage safe practices. Concurrent Ruby provides more safe concurrency abstractions than any other Ruby library, many of which support the mantra of ["Do not communicate by sharing memory; instead, share memory by communicating"](https://blog.golang.org/share-memory-by-communicating). Concurrent Ruby is also the only Ruby library which provides a full suite of thread safe and immutable variable types and data structures.
51
59
 
52
60
  ## Features & Documentation
53
61
 
@@ -61,55 +69,63 @@ This library contains a variety of concurrency abstractions at high and low leve
61
69
 
62
70
  #### General-purpose Concurrency Abstractions
63
71
 
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.
65
- * [Atom](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Atom.html): A way to manage shared, synchronous, independent state.
72
+ * [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).
66
73
  * [Future](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Future.html): An asynchronous operation that produces a value.
67
74
  * [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.
68
75
  * [Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html): Similar to Futures, with more features.
69
76
  * [ScheduledTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ScheduledTask.html): Like a Future scheduled for a specific future time.
70
77
  * [TimerTask](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TimerTask.html): A Thread that periodically wakes up to perform work at regular intervals.
71
78
 
72
- #### Thread-safe Value Objects
79
+ #### Thread-safe Value Objects, Structures, and Collections
80
+
81
+ Collection classes that were originally part of the (deprecated) `thread_safe` gem:
73
82
 
74
- * `Maybe` A thread-safe, immutable object representing an optional value, based on
83
+ * [Array](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Array.html) A thread-safe subclass of Ruby's standard [Array](http://ruby-doc.org/core-2.2.0/Array.html).
84
+ * [Hash](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Hash.html) A thread-safe subclass of Ruby's standard [Hash](http://ruby-doc.org/core-2.2.0/Hash.html).
85
+ * [Map](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Map.html) A hash-like object that should have much better performance characteristics, especially under high concurrency, than `Concurrent::Hash`.
86
+ * [Tuple](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Tuple.html) A fixed size array with volatile (synchronized, thread safe) getters/setters.
87
+
88
+ Value objects inspired by other languages:
89
+
90
+ * [Maybe](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Maybe.html) A thread-safe, immutable object representing an optional value, based on
75
91
  [Haskell Data.Maybe](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html).
76
- * `Delay` Lazy evaluation of a block yielding an immutable result. Based on Clojure's
77
- [delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html).
92
+ * [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).
93
+
94
+ Structure classes derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/Struct.html):
78
95
 
79
- #### Thread-safe Structures
96
+ * [ImmutableStruct](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ImmutableStruct.html) Immutable struct where values are set at construction and cannot be changed later.
97
+ * [MutableStruct](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MutableStruct.html) Synchronized, mutable struct where values can be safely changed at any time.
98
+ * [SettableStruct](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/SettableStruct.html) Synchronized, write-once struct where values can be set at most once, either at construction or any time thereafter.
80
99
 
81
- Derived from Ruby's [Struct](http://ruby-doc.org/core-2.2.0/Struct.html):
100
+ Thread-safe variables:
82
101
 
83
- * `ImmutableStruct` Immutable struct where values are set at construction and cannot be changed later.
84
- * `MutableStruct` Synchronized, mutable struct where values can be safely changed at any time.
85
- * `SettableStruct` Synchronized, write-once struct where values can be set at most once, either at construction or any time thereafter.
102
+ * [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).
103
+ * [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).
104
+ * [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicBoolean.html) A boolean value that can be updated atomically.
105
+ * [AtomicFixnum](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicFixnum.html) A numeric value that can be updated atomically.
106
+ * [AtomicReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MutexAtomic.html) An object reference that may be updated atomically.
107
+ * [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).
108
+ * [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$).
109
+ * [ThreadLocalVar](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html) A variable where the value is different for each thread.
110
+ * [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).
86
111
 
87
112
  #### Java-inspired ThreadPools and Other Executors
88
113
 
89
- * See [ThreadPool](http://ruby-concurrency.github.io/concurrent-ruby/file.thread_pools.html) overview, which also contains a list of other Executors available.
114
+ * See the [thread pool](http://ruby-concurrency.github.io/concurrent-ruby/file.thread_pools.html) overview, which also contains a list of other Executors available.
90
115
 
91
116
  #### Thread Synchronization Classes and Algorithms
92
117
 
93
- * [CountdownLatch](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CountDownLatch.html)
94
- * [CyclicBarrier](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CyclicBarrier.html)
95
- * [Event](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Event.html)
96
- * [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html)
97
-
98
- #### Thread-safe Variables
99
-
100
- * [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicBoolean.html)
101
- * [AtomicFixnum](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicFixnum.html)
102
- * [AtomicReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MutexAtomic.html)
103
- * [I-Structures](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/IVar.html) (IVar)
104
- * [M-Structures](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) (MVar)
105
- * [Thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html)
106
- * [Software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar)
107
- * [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html)
108
- * [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html)
118
+ * [CountDownLatch](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CountDownLatch.html) A synchronization object that allows one thread to wait on multiple other threads.
119
+ * [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.
120
+ * [Event](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Event.html) Old school kernel-style event.
121
+ * [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.
122
+ * [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReadWriteLock.html) A lock that supports multiple readers but only one writer.
123
+ * [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ReentrantReadWriteLock.html) A read/write lock with reentrant and upgrade features.
124
+ * [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html) A counting-based locking mechanism that uses permits.
109
125
 
110
126
  ### Edge Features
111
127
 
112
- These are available in the `concurrent-ruby-edge` companion gem, installed with `gem install concurrent-ruby-edge`.
128
+ These are available in the `concurrent-ruby-edge` companion gem.
113
129
 
114
130
  These features are under active development and may change frequently. They are expected not to
115
131
  keep backward compatibility (there may also lack tests and documentation). Semantic versions will
@@ -117,74 +133,42 @@ be obeyed though. Features developed in `concurrent-ruby-edge` are expected to m
117
133
 
118
134
  * [Actor](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Actor.html):
119
135
  Implements the Actor Model, where concurrent actors exchange messages.
120
- * [new Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/FutureShortcuts.html) - new
121
- unified implementation of Futures and Promises which combines Features of previous `Future`,
122
- `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
123
- new synchronization layer to make all the features **non-blocking** and **lock-free** with exception of obviously blocking
136
+ * [New Future Framework](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/FutureShortcuts.html):
137
+ Unified implementation of futures and promises which combines features of previous `Future`,
138
+ `Promise`, `IVar`, `Event`, `dataflow`, `Delay`, and `TimerTask` into a single framework. It extensively uses the
139
+ new synchronization layer to make all the features **non-blocking** and **lock-free**, with the exception of obviously blocking
124
140
  operations like `#wait`, `#value`. It also offers better performance.
125
- * [Agent](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Agent.html): A single atomic value that represents an identity.
126
- * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Channel.html):
127
- Communicating Sequential Processes (CSP).
128
- * [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html)
141
+ * [Channel](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/Channel.html):
142
+ Communicating Sequential Processes ([CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)).
143
+ Functionally equivalent to Go [channels](https://tour.golang.org/concurrency/2) with additional
144
+ inspiration from Clojure [core.async](https://clojure.github.io/core.async/).
129
145
  * [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
130
- * [Atomic Markable Reference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/AtomicMarkableReference.html)
131
- * [LockFreeLinked Set](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeLinkedSet.html)
146
+ * [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/AtomicMarkableReference.html)
147
+ * [LockFreeLinkedSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeLinkedSet.html)
132
148
  * [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge/LockFreeStack.html)
133
149
 
134
150
  #### Statuses:
135
151
 
136
152
  *Why are these not in core?*
137
153
 
138
- - **Actor** - Partial documentation and tests; stability is good.
139
- - **Future/Promise Framework** - API changes; partial documentation and tests; stability good.
140
- - **Agent** - Incomplete behaviour compared to Clojure's models; stability good.
141
- - **Channel** - Missing documentation; limted features; stability good.
142
- - **Exchanger** - Known race condition requiring a new implementation.
154
+ - **Actor** - Partial documentation and tests; depends on new future/promise framework; stability is good.
155
+ - **Channel** - Brand new implementation; partial documentation and tests; stability is good.
156
+ - **Future/Promise Framework** - API changes; partial documentation and tests; stability is good.
143
157
  - **LazyRegister** - Missing documentation and tests.
144
- - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Needs real world battle testing
158
+ - **AtomicMarkableReference, LockFreeLinkedSet, LockFreeStack** - Need real world battle testing.
145
159
 
146
160
  ## Usage
147
161
 
148
- All abstractions within this gem can be loaded simply by requiring it:
162
+ Everything within this gem can be loaded simply by requiring it:
149
163
 
150
164
  ```ruby
151
165
  require 'concurrent'
152
166
  ```
153
167
 
154
- To reduce the amount of code loaded at runtime, subsets of this gem can be required:
168
+ To use the tools in the Edge gem it must be required separately:
155
169
 
156
170
  ```ruby
157
- require 'concurrent' # everything
158
-
159
- # groups
160
-
161
- require 'concurrent/atomics' # atomic and thread synchronization classes
162
- require 'concurrent/executors' # Thread pools and other executors
163
-
164
- # individual abstractions
165
-
166
- require 'concurrent/async' # Concurrent::Async
167
- require 'concurrent/atom' # Concurrent::Atom
168
- require 'concurrent/dataflow' # Concurrent::dataflow
169
- require 'concurrent/delay' # Concurrent::Delay
170
- require 'concurrent/future' # Concurrent::Future
171
- require 'concurrent/immutable_struct' # Concurrent::ImmutableStruct
172
- require 'concurrent/ivar' # Concurrent::IVar
173
- require 'concurrent/maybe' # Concurrent::Maybe
174
- require 'concurrent/mutable_struct' # Concurrent::MutableStruct
175
- require 'concurrent/mvar' # Concurrent::MVar
176
- require 'concurrent/promise' # Concurrent::Promise
177
- require 'concurrent/scheduled_task' # Concurrent::ScheduledTask
178
- require 'concurrent/settable_struct' # Concurrent::SettableStruct
179
- require 'concurrent/timer_task' # Concurrent::TimerTask
180
- require 'concurrent/tvar' # Concurrent::TVar
181
-
182
- # experimental - available in `concurrent-ruby-edge` companion gem
183
-
184
- require 'concurrent/actor' # Concurrent::Actor and supporting code
185
- require 'concurrent/edge/future' # new Future Framework
186
- require 'concurrent/agent' # Concurrent::Agent
187
- require 'concurrent/channel ' # Concurrent::Channel and supporting code
171
+ require 'concurrent-edge'
188
172
  ```
189
173
 
190
174
  If the library does not behave as expected, `Concurrent.use_stdlib_logger(Logger::DEBUG)` could help to reveal the problem.
@@ -203,6 +187,23 @@ gem 'concurrent-ruby'
203
187
 
204
188
  and run `bundle install` from your shell.
205
189
 
190
+ ### Edge Gem Installation
191
+
192
+ The Edge gem must be installed separately from the core gem:
193
+
194
+ ```shell
195
+ gem install concurrent-ruby-edge
196
+ ```
197
+
198
+ or add the following line to Gemfile:
199
+
200
+ ```ruby
201
+ gem 'concurrent-ruby-edge'
202
+ ```
203
+
204
+ and run `bundle install` from your shell.
205
+
206
+
206
207
  ### C Extensions for MRI
207
208
 
208
209
  Potential performance improvements may be achieved under MRI by installing optional C extensions.
@@ -236,55 +237,20 @@ and load the appropriate C extensions.
236
237
  No gems should depend on `concurrent-ruby-ext`. Doing so will force C extensions on your users.
237
238
  The best practice is to depend on `concurrent-ruby` and let users to decide if they want C extensions.
238
239
 
239
- ### Building
240
-
241
- All published versions of this gem (core, extension, and several platform-specific packages) are compiled,
242
- packaged, tested, and published using an open, [automated process](https://github.com/ruby-concurrency/rake-compiler-dev-box).
243
- This process can also be used to create pre-compiled binaries of the extension gem for virtally
244
- any platform. *Documentation is forthcoming...*
245
-
246
- ```
247
- *MRI only*
248
- bundle exec rake build:native # Build concurrent-ruby-ext-<version>-<platform>.gem into the pkg dir
249
- bundle exec rake compile:extension # Compile extension
250
-
251
- *JRuby only*
252
- bundle exec rake build # Build JRuby-specific core gem (alias for `build:core`)
253
- bundle exec rake build:core # Build concurrent-ruby-<version>-java.gem into the pkg directory
254
-
255
- *All except JRuby*
256
- bundle exec rake build:core # Build concurrent-ruby-<version>.gem into the pkg directory
257
- bundle exec rake build:ext # Build concurrent-ruby-ext-<version>.gem into the pkg directory
258
-
259
- *When Docker IS installed*
260
- bundle exec rake build:windows # Build the windows binary <version> gems per rake-compiler-dock
261
- bundle exec rake build # Build core, extension, and edge gems, including Windows binaries
262
-
263
- *When Docker is NOT installed*
264
- bundle exec rake build # Build core, extension, and edge gems (excluding Windows binaries)
265
-
266
- *All*
267
- bundle exec rake clean # Remove any temporary products
268
- bundle exec rake clobber # Remove any generated file
269
- bundle exec rake compile # Compile all the extensions
270
- ```
271
-
272
240
  ## Maintainers
273
241
 
274
242
  * [Jerry D'Antonio](https://github.com/jdantonio) (creator)
243
+ * [Petr Chalupa](https://github.com/pitr-ch)
275
244
  * [Michele Della Torre](https://github.com/mighe)
276
245
  * [Chris Seaton](https://github.com/chrisseaton)
277
- * [Lucas Allan](https://github.com/lucasallan)
278
- * [Petr Chalupa](https://github.com/pitr-ch)
279
246
  * [Paweł Obrok](https://github.com/obrok)
247
+ * [Lucas Allan](https://github.com/lucasallan)
280
248
 
281
- ## Contributing
249
+ ### Special Thanks
282
250
 
283
- 1. Fork it
284
- 2. Create your feature branch (`git checkout -b my-new-feature`)
285
- 3. Commit your changes (`git commit -am 'Add some feature'`)
286
- 4. Push to the branch (`git push origin my-new-feature`)
287
- 5. Create new Pull Request
251
+ * [Brian Durand](https://github.com/bdurand) for the `ref` gem
252
+ * [Charles Oliver Nutter](https://github.com/headius) for the `atomic` and `thread_safe` gems
253
+ * [thedarkone](https://github.com/thedarkone) for the `thread_safe` gem
288
254
 
289
255
  ## License and Copyright
290
256
 
@@ -1,19 +1,23 @@
1
1
  require 'concurrent/version'
2
-
3
- require 'concurrent/synchronization'
4
-
2
+ require 'concurrent/constants'
3
+ require 'concurrent/errors'
5
4
  require 'concurrent/configuration'
6
5
 
7
6
  require 'concurrent/atomics'
8
- require 'concurrent/errors'
9
7
  require 'concurrent/executors'
10
- require 'concurrent/utilities'
8
+ require 'concurrent/synchronization'
11
9
 
12
10
  require 'concurrent/atomic/atomic_reference'
11
+ require 'concurrent/agent'
13
12
  require 'concurrent/atom'
13
+ require 'concurrent/array'
14
+ require 'concurrent/hash'
15
+ require 'concurrent/map'
16
+ require 'concurrent/tuple'
14
17
  require 'concurrent/async'
15
18
  require 'concurrent/dataflow'
16
19
  require 'concurrent/delay'
20
+ require 'concurrent/exchanger'
17
21
  require 'concurrent/future'
18
22
  require 'concurrent/immutable_struct'
19
23
  require 'concurrent/ivar'
@@ -26,6 +30,11 @@ require 'concurrent/settable_struct'
26
30
  require 'concurrent/timer_task'
27
31
  require 'concurrent/tvar'
28
32
 
33
+ require 'concurrent/thread_safe/synchronized_delegator'
34
+ require 'concurrent/thread_safe/util'
35
+
36
+ require 'concurrent/options'
37
+
29
38
  # @!macro [new] internal_implementation_note
30
39
  #
31
40
  # @note **Private Implementation:** This abstraction is a private, internal
@@ -0,0 +1,587 @@
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
+ super(message)
170
+ end
171
+ end
172
+
173
+ # Raised when a new value obtained during action processing or at `#restart`
174
+ # fails validation.
175
+ class ValidationError < Error
176
+ def initialize(message = nil)
177
+ message ||= 'invalid value'
178
+ super(message)
179
+ end
180
+ end
181
+
182
+ # The error mode this Agent is operating in. See {#initialize} for details.
183
+ attr_reader :error_mode
184
+
185
+ # Create a new `Agent` with the given initial value and options.
186
+ #
187
+ # The `:validator` option must be `nil` or a side-effect free proc/lambda
188
+ # which takes one argument. On any intended value change the validator, if
189
+ # provided, will be called. If the new value is invalid the validator should
190
+ # return `false` or raise an error.
191
+ #
192
+ # The `:error_handler` option must be `nil` or a proc/lambda which takes two
193
+ # arguments. When an action raises an error or validation fails, either by
194
+ # returning false or raising an error, the error handler will be called. The
195
+ # arguments to the error handler will be a reference to the agent itself and
196
+ # the error object which was raised.
197
+ #
198
+ # The `:error_mode` may be either `:continue` (the default if an error
199
+ # handler is given) or `:fail` (the default if error handler nil or not
200
+ # given).
201
+ #
202
+ # If an action being run by the agent throws an error or doesn't pass
203
+ # validation the error handler, if present, will be called. After the
204
+ # handler executes if the error mode is `:continue` the Agent will continue
205
+ # as if neither the action that caused the error nor the error itself ever
206
+ # happened.
207
+ #
208
+ # If the mode is `:fail` the Agent will become {#failed?} and will stop
209
+ # accepting new action dispatches. Any previously queued actions will be
210
+ # held until {#restart} is called. The {#value} method will still work,
211
+ # returning the value of the Agent before the error.
212
+ #
213
+ # @param [Object] initial the initial value
214
+ # @param [Hash] opts the configuration options
215
+ #
216
+ # @option opts [Symbol] :error_mode either `:continue` or `:fail`
217
+ # @option opts [nil, Proc] :error_handler the (optional) error handler
218
+ # @option opts [nil, Proc] :validator the (optional) validation procedure
219
+ def initialize(initial, opts = {})
220
+ super()
221
+ synchronize { ns_initialize(initial, opts) }
222
+ end
223
+
224
+ # The current value (state) of the Agent, irrespective of any pending or
225
+ # in-progress actions. The value is always available and is non-blocking.
226
+ #
227
+ # @return [Object] the current value
228
+ def value
229
+ @current.value # TODO (pitr 12-Sep-2015): broken unsafe read?
230
+ end
231
+
232
+ alias_method :deref, :value
233
+
234
+ # When {#failed?} and {#error_mode} is `:fail`, returns the error object
235
+ # which caused the failure, else `nil`. When {#error_mode} is `:continue`
236
+ # will *always* return `nil`.
237
+ #
238
+ # @return [nil, Error] the error which caused the failure when {#failed?}
239
+ def error
240
+ @error.value
241
+ end
242
+
243
+ alias_method :reason, :error
244
+
245
+ # @!macro [attach] agent_send
246
+ #
247
+ # Dispatches an action to the Agent and returns immediately. Subsequently,
248
+ # in a thread from a thread pool, the {#value} will be set to the return
249
+ # value of the action. Action dispatches are only allowed when the Agent
250
+ # is not {#failed?}.
251
+ #
252
+ # The action must be a block/proc/lambda which takes 1 or more arguments.
253
+ # The first argument is the current {#value} of the Agent. Any arguments
254
+ # passed to the send method via the `args` parameter will be passed to the
255
+ # action as the remaining arguments. The action must return the new value
256
+ # of the Agent.
257
+ #
258
+ # * {#send} and {#send!} should be used for actions that are CPU limited
259
+ # * {#send_off}, {#send_off!}, and {#<<} are appropriate for actions that
260
+ # may block on IO
261
+ # * {#send_via} and {#send_via!} are used when a specific executor is to
262
+ # be used for the action
263
+ #
264
+ # @param [Array<Object>] args zero or more arguments to be passed to
265
+ # the action
266
+ # @param [Proc] action the action dispatch to be enqueued
267
+ #
268
+ # @yield [agent, value, *args] process the old value and return the new
269
+ # @yieldparam [Object] value the current {#value} of the Agent
270
+ # @yieldparam [Array<Object>] args zero or more arguments to pass to the
271
+ # action
272
+ # @yieldreturn [Object] the new value of the Agent
273
+ #
274
+ # @!macro [attach] send_return
275
+ # @return [Boolean] true if the action is successfully enqueued, false if
276
+ # the Agent is {#failed?}
277
+ def send(*args, &action)
278
+ enqueue_action_job(action, args, Concurrent.global_fast_executor)
279
+ end
280
+
281
+ # @!macro agent_send
282
+ #
283
+ # @!macro [attach] send_bang_return_and_raise
284
+ # @return [Boolean] true if the action is successfully enqueued
285
+ # @raise [Concurrent::Agent::Error] if the Agent is {#failed?}
286
+ def send!(*args, &action)
287
+ raise Error.new unless send(*args, &action)
288
+ true
289
+ end
290
+
291
+ # @!macro agent_send
292
+ # @!macro send_return
293
+ def send_off(*args, &action)
294
+ enqueue_action_job(action, args, Concurrent.global_io_executor)
295
+ end
296
+
297
+ alias_method :post, :send_off
298
+
299
+ # @!macro agent_send
300
+ # @!macro send_bang_return_and_raise
301
+ def send_off!(*args, &action)
302
+ raise Error.new unless send_off(*args, &action)
303
+ true
304
+ end
305
+
306
+ # @!macro agent_send
307
+ # @!macro send_return
308
+ # @param [Concurrent::ExecutorService] executor the executor on which the
309
+ # action is to be dispatched
310
+ def send_via(executor, *args, &action)
311
+ enqueue_action_job(action, args, executor)
312
+ end
313
+
314
+ # @!macro agent_send
315
+ # @!macro send_bang_return_and_raise
316
+ # @param [Concurrent::ExecutorService] executor the executor on which the
317
+ # action is to be dispatched
318
+ def send_via!(executor, *args, &action)
319
+ raise Error.new unless send_via(executor, *args, &action)
320
+ true
321
+ end
322
+
323
+ # Dispatches an action to the Agent and returns immediately. Subsequently,
324
+ # in a thread from a thread pool, the {#value} will be set to the return
325
+ # value of the action. Appropriate for actions that may block on IO.
326
+ #
327
+ # @param [Proc] action the action dispatch to be enqueued
328
+ # @return [Concurrent::Agent] self
329
+ # @see {#send_off}
330
+ def <<(action)
331
+ send_off(&action)
332
+ self
333
+ end
334
+
335
+ # Blocks the current thread (indefinitely!) until all actions dispatched
336
+ # thus far, from this thread or nested by the Agent, have occurred. Will
337
+ # block when {#failed?}. Will never return if a failed Agent is {#restart}
338
+ # with `:clear_actions` true.
339
+ #
340
+ # Returns a reference to `self` to support method chaining:
341
+ #
342
+ # ```
343
+ # current_value = agent.await.value
344
+ # ```
345
+ #
346
+ # @return [Boolean] self
347
+ #
348
+ # @!macro agent_await_warning
349
+ def await
350
+ wait(nil)
351
+ self
352
+ end
353
+
354
+ # Blocks the current thread until all actions dispatched thus far, from this
355
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
356
+ # has elapsed.
357
+ #
358
+ # @param [Float] timeout the maximum number of seconds to wait
359
+ # @return [Boolean] true if all actions complete before timeout else false
360
+ #
361
+ # @!macro agent_await_warning
362
+ def await_for(timeout)
363
+ wait(timeout.to_f)
364
+ end
365
+
366
+ # Blocks the current thread until all actions dispatched thus far, from this
367
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
368
+ # has elapsed.
369
+ #
370
+ # @param [Float] timeout the maximum number of seconds to wait
371
+ # @return [Boolean] true if all actions complete before timeout
372
+ #
373
+ # @raise [Concurrent::TimeoutError] when timout is reached
374
+ #
375
+ # @!macro agent_await_warning
376
+ def await_for!(timeout)
377
+ raise Concurrent::TimeoutError unless wait(timeout.to_f)
378
+ true
379
+ end
380
+
381
+ # Blocks the current thread until all actions dispatched thus far, from this
382
+ # thread or nested by the Agent, have occurred, or the timeout (in seconds)
383
+ # has elapsed. Will block indefinitely when timeout is nil or not given.
384
+ #
385
+ # Provided mainly for consistency with other classes in this library. Prefer
386
+ # the various `await` methods instead.
387
+ #
388
+ # @param [Float] timeout the maximum number of seconds to wait
389
+ # @return [Boolean] true if all actions complete before timeout else false
390
+ #
391
+ # @!macro agent_await_warning
392
+ def wait(timeout = nil)
393
+ latch = Concurrent::CountDownLatch.new(1)
394
+ enqueue_await_job(latch)
395
+ latch.wait(timeout)
396
+ end
397
+
398
+ # Is the Agent in a failed state?
399
+ #
400
+ # @see {#restart}
401
+ def failed?
402
+ !@error.value.nil?
403
+ end
404
+
405
+ alias_method :stopped?, :failed?
406
+
407
+ # When an Agent is {#failed?}, changes the Agent {#value} to `new_value`
408
+ # then un-fails the Agent so that action dispatches are allowed again. If
409
+ # the `:clear_actions` option is give and true, any actions queued on the
410
+ # Agent that were being held while it was failed will be discarded,
411
+ # otherwise those held actions will proceed. The `new_value` must pass the
412
+ # validator if any, or `restart` will raise an exception and the Agent will
413
+ # remain failed with its old {#value} and {#error}. Observers, if any, will
414
+ # not be notified of the new state.
415
+ #
416
+ # @param [Object] new_value the new value for the Agent once restarted
417
+ # @param [Hash] opts the configuration options
418
+ # @option opts [Symbol] :clear_actions true if all enqueued but unprocessed
419
+ # actions should be discarded on restart, else false (default: false)
420
+ # @return [Boolean] true
421
+ #
422
+ # @raise [Concurrent:AgentError] when not failed
423
+ def restart(new_value, opts = {})
424
+ clear_actions = opts.fetch(:clear_actions, false)
425
+ synchronize do
426
+ raise Error.new('agent is not failed') unless failed?
427
+ raise ValidationError unless ns_validate(new_value)
428
+ @current.value = new_value
429
+ @error.value = nil
430
+ @queue.clear if clear_actions
431
+ ns_post_next_job unless @queue.empty?
432
+ end
433
+ true
434
+ end
435
+
436
+ class << self
437
+
438
+ # Blocks the current thread (indefinitely!) until all actions dispatched
439
+ # thus far to all the given Agents, from this thread or nested by the
440
+ # given Agents, have occurred. Will block when any of the agents are
441
+ # failed. Will never return if a failed Agent is restart with
442
+ # `:clear_actions` true.
443
+ #
444
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
445
+ # @return [Boolean] true
446
+ #
447
+ # @!macro agent_await_warning
448
+ def await(*agents)
449
+ agents.each { |agent| agent.await }
450
+ true
451
+ end
452
+
453
+ # Blocks the current thread until all actions dispatched thus far to all
454
+ # the given Agents, from this thread or nested by the given Agents, have
455
+ # occurred, or the timeout (in seconds) has elapsed.
456
+ #
457
+ # @param [Float] timeout the maximum number of seconds to wait
458
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
459
+ # @return [Boolean] true if all actions complete before timeout else false
460
+ #
461
+ # @!macro agent_await_warning
462
+ def await_for(timeout, *agents)
463
+ end_at = Concurrent.monotonic_time + timeout.to_f
464
+ ok = agents.length.times do |i|
465
+ break false if (delay = end_at - Concurrent.monotonic_time) < 0
466
+ break false unless agents[i].await_for(delay)
467
+ end
468
+ !!ok
469
+ end
470
+
471
+ # Blocks the current thread until all actions dispatched thus far to all
472
+ # the given Agents, from this thread or nested by the given Agents, have
473
+ # occurred, or the timeout (in seconds) has elapsed.
474
+ #
475
+ # @param [Float] timeout the maximum number of seconds to wait
476
+ # @param [Array<Concurrent::Agent>] agents the Agents on which to wait
477
+ # @return [Boolean] true if all actions complete before timeout
478
+ #
479
+ # @raise [Concurrent::TimeoutError] when timout is reached
480
+ # @!macro agent_await_warning
481
+ def await_for!(timeout, *agents)
482
+ raise Concurrent::TimeoutError unless await_for(timeout, *agents)
483
+ true
484
+ end
485
+ end
486
+
487
+ private
488
+
489
+ def ns_initialize(initial, opts)
490
+ @error_mode = opts[:error_mode]
491
+ @error_handler = opts[:error_handler]
492
+
493
+ if @error_mode && !ERROR_MODES.include?(@error_mode)
494
+ raise ArgumentError.new('unrecognized error mode')
495
+ elsif @error_mode.nil?
496
+ @error_mode = @error_handler ? :continue : :fail
497
+ end
498
+
499
+ @error_handler ||= DEFAULT_ERROR_HANDLER
500
+ @validator = opts.fetch(:validator, DEFAULT_VALIDATOR)
501
+ @current = Concurrent::AtomicReference.new(initial)
502
+ @error = Concurrent::AtomicReference.new(nil)
503
+ @caller = Concurrent::ThreadLocalVar.new(nil)
504
+ @queue = []
505
+
506
+ self.observers = Collection::CopyOnNotifyObserverSet.new
507
+ end
508
+
509
+ def enqueue_action_job(action, args, executor)
510
+ raise ArgumentError.new('no action given') unless action
511
+ job = Job.new(action, args, executor, @caller.value || Thread.current.object_id)
512
+ synchronize { ns_enqueue_job(job) }
513
+ end
514
+
515
+ def enqueue_await_job(latch)
516
+ synchronize do
517
+ if (index = ns_find_last_job_for_thread)
518
+ job = Job.new(AWAIT_ACTION, [latch], Concurrent.global_immediate_executor,
519
+ Thread.current.object_id)
520
+ ns_enqueue_job(job, index+1)
521
+ else
522
+ latch.count_down
523
+ true
524
+ end
525
+ end
526
+ end
527
+
528
+ def ns_enqueue_job(job, index = nil)
529
+ # a non-nil index means this is an await job
530
+ return false if index.nil? && failed?
531
+ index ||= @queue.length
532
+ @queue.insert(index, job)
533
+ # if this is the only job, post to executor
534
+ ns_post_next_job if @queue.length == 1
535
+ true
536
+ end
537
+
538
+ def ns_post_next_job
539
+ @queue.first.executor.post { execute_next_job }
540
+ end
541
+
542
+ def execute_next_job
543
+ job = synchronize { @queue.first }
544
+ old_value = @current.value
545
+
546
+ @caller.value = job.caller # for nested actions
547
+ new_value = job.action.call(old_value, *job.args)
548
+ @caller.value = nil
549
+
550
+ return if new_value == AWAIT_FLAG
551
+
552
+ if ns_validate(new_value)
553
+ @current.value = new_value
554
+ observers.notify_observers(Time.now, old_value, new_value)
555
+ else
556
+ handle_error(ValidationError.new)
557
+ end
558
+ rescue => error
559
+ handle_error(error)
560
+ ensure
561
+ synchronize do
562
+ @queue.shift
563
+ unless failed? || @queue.empty?
564
+ ns_post_next_job
565
+ end
566
+ end
567
+ end
568
+
569
+ def ns_validate(value)
570
+ @validator.call(value)
571
+ rescue
572
+ false
573
+ end
574
+
575
+ def handle_error(error)
576
+ # stop new jobs from posting
577
+ @error.value = error if @error_mode == :fail
578
+ @error_handler.call(self, error)
579
+ rescue
580
+ # do nothing
581
+ end
582
+
583
+ def ns_find_last_job_for_thread
584
+ @queue.rindex { |job| job.caller == Thread.current.object_id }
585
+ end
586
+ end
587
+ end