concurrent-ruby 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +478 -0
  3. data/Gemfile +41 -0
  4. data/LICENSE.md +23 -0
  5. data/README.md +381 -0
  6. data/Rakefile +327 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby.rb +1 -0
  23. data/lib/concurrent.rb +134 -0
  24. data/lib/concurrent/agent.rb +587 -0
  25. data/lib/concurrent/array.rb +66 -0
  26. data/lib/concurrent/async.rb +459 -0
  27. data/lib/concurrent/atom.rb +222 -0
  28. data/lib/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  29. data/lib/concurrent/atomic/atomic_boolean.rb +126 -0
  30. data/lib/concurrent/atomic/atomic_fixnum.rb +143 -0
  31. data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
  32. data/lib/concurrent/atomic/atomic_reference.rb +204 -0
  33. data/lib/concurrent/atomic/count_down_latch.rb +100 -0
  34. data/lib/concurrent/atomic/cyclic_barrier.rb +128 -0
  35. data/lib/concurrent/atomic/event.rb +109 -0
  36. data/lib/concurrent/atomic/java_count_down_latch.rb +42 -0
  37. data/lib/concurrent/atomic/java_thread_local_var.rb +37 -0
  38. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  39. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  40. data/lib/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  41. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  42. data/lib/concurrent/atomic/read_write_lock.rb +254 -0
  43. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +379 -0
  44. data/lib/concurrent/atomic/ruby_thread_local_var.rb +161 -0
  45. data/lib/concurrent/atomic/semaphore.rb +145 -0
  46. data/lib/concurrent/atomic/thread_local_var.rb +104 -0
  47. data/lib/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  48. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  49. data/lib/concurrent/atomics.rb +10 -0
  50. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  51. data/lib/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  52. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  53. data/lib/concurrent/collection/lock_free_stack.rb +158 -0
  54. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  55. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  56. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  57. data/lib/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  58. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  59. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  60. data/lib/concurrent/concern/deprecation.rb +34 -0
  61. data/lib/concurrent/concern/dereferenceable.rb +73 -0
  62. data/lib/concurrent/concern/logging.rb +32 -0
  63. data/lib/concurrent/concern/obligation.rb +220 -0
  64. data/lib/concurrent/concern/observable.rb +110 -0
  65. data/lib/concurrent/concurrent_ruby.jar +0 -0
  66. data/lib/concurrent/configuration.rb +184 -0
  67. data/lib/concurrent/constants.rb +8 -0
  68. data/lib/concurrent/dataflow.rb +81 -0
  69. data/lib/concurrent/delay.rb +199 -0
  70. data/lib/concurrent/errors.rb +69 -0
  71. data/lib/concurrent/exchanger.rb +352 -0
  72. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  73. data/lib/concurrent/executor/cached_thread_pool.rb +62 -0
  74. data/lib/concurrent/executor/executor_service.rb +185 -0
  75. data/lib/concurrent/executor/fixed_thread_pool.rb +206 -0
  76. data/lib/concurrent/executor/immediate_executor.rb +66 -0
  77. data/lib/concurrent/executor/indirect_immediate_executor.rb +44 -0
  78. data/lib/concurrent/executor/java_executor_service.rb +91 -0
  79. data/lib/concurrent/executor/java_single_thread_executor.rb +29 -0
  80. data/lib/concurrent/executor/java_thread_pool_executor.rb +123 -0
  81. data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
  82. data/lib/concurrent/executor/ruby_single_thread_executor.rb +22 -0
  83. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +362 -0
  84. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  85. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  86. data/lib/concurrent/executor/serialized_execution.rb +107 -0
  87. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  88. data/lib/concurrent/executor/simple_executor_service.rb +100 -0
  89. data/lib/concurrent/executor/single_thread_executor.rb +56 -0
  90. data/lib/concurrent/executor/thread_pool_executor.rb +87 -0
  91. data/lib/concurrent/executor/timer_set.rb +173 -0
  92. data/lib/concurrent/executors.rb +20 -0
  93. data/lib/concurrent/future.rb +141 -0
  94. data/lib/concurrent/hash.rb +59 -0
  95. data/lib/concurrent/immutable_struct.rb +93 -0
  96. data/lib/concurrent/ivar.rb +207 -0
  97. data/lib/concurrent/map.rb +337 -0
  98. data/lib/concurrent/maybe.rb +229 -0
  99. data/lib/concurrent/mutable_struct.rb +229 -0
  100. data/lib/concurrent/mvar.rb +242 -0
  101. data/lib/concurrent/options.rb +42 -0
  102. data/lib/concurrent/promise.rb +579 -0
  103. data/lib/concurrent/promises.rb +2167 -0
  104. data/lib/concurrent/re_include.rb +58 -0
  105. data/lib/concurrent/scheduled_task.rb +318 -0
  106. data/lib/concurrent/set.rb +66 -0
  107. data/lib/concurrent/settable_struct.rb +129 -0
  108. data/lib/concurrent/synchronization.rb +30 -0
  109. data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  110. data/lib/concurrent/synchronization/abstract_object.rb +24 -0
  111. data/lib/concurrent/synchronization/abstract_struct.rb +160 -0
  112. data/lib/concurrent/synchronization/condition.rb +60 -0
  113. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  114. data/lib/concurrent/synchronization/jruby_object.rb +45 -0
  115. data/lib/concurrent/synchronization/lock.rb +36 -0
  116. data/lib/concurrent/synchronization/lockable_object.rb +74 -0
  117. data/lib/concurrent/synchronization/mri_object.rb +44 -0
  118. data/lib/concurrent/synchronization/mutex_lockable_object.rb +76 -0
  119. data/lib/concurrent/synchronization/object.rb +183 -0
  120. data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
  121. data/lib/concurrent/synchronization/rbx_object.rb +49 -0
  122. data/lib/concurrent/synchronization/truffleruby_object.rb +47 -0
  123. data/lib/concurrent/synchronization/volatile.rb +36 -0
  124. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  125. data/lib/concurrent/thread_safe/util.rb +16 -0
  126. data/lib/concurrent/thread_safe/util/adder.rb +74 -0
  127. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  128. data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
  129. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  130. data/lib/concurrent/thread_safe/util/striped64.rb +246 -0
  131. data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
  132. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  133. data/lib/concurrent/timer_task.rb +334 -0
  134. data/lib/concurrent/tuple.rb +86 -0
  135. data/lib/concurrent/tvar.rb +258 -0
  136. data/lib/concurrent/utility/at_exit.rb +97 -0
  137. data/lib/concurrent/utility/engine.rb +56 -0
  138. data/lib/concurrent/utility/monotonic_time.rb +58 -0
  139. data/lib/concurrent/utility/native_extension_loader.rb +79 -0
  140. data/lib/concurrent/utility/native_integer.rb +53 -0
  141. data/lib/concurrent/utility/processor_counter.rb +158 -0
  142. data/lib/concurrent/version.rb +3 -0
  143. metadata +193 -0
@@ -0,0 +1,141 @@
1
+ require 'thread'
2
+ require 'concurrent/constants'
3
+ require 'concurrent/errors'
4
+ require 'concurrent/ivar'
5
+ require 'concurrent/executor/safe_task_executor'
6
+
7
+ require 'concurrent/options'
8
+
9
+ # TODO (pitr-ch 14-Mar-2017): deprecate, Future, Promise, etc.
10
+
11
+
12
+ module Concurrent
13
+
14
+ # {include:file:docs-source/future.md}
15
+ #
16
+ # @!macro copy_options
17
+ #
18
+ # @see http://ruby-doc.org/stdlib-2.1.1/libdoc/observer/rdoc/Observable.html Ruby Observable module
19
+ # @see http://clojuredocs.org/clojure_core/clojure.core/future Clojure's future function
20
+ # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html java.util.concurrent.Future
21
+ class Future < IVar
22
+
23
+ # Create a new `Future` in the `:unscheduled` state.
24
+ #
25
+ # @yield the asynchronous operation to perform
26
+ #
27
+ # @!macro executor_and_deref_options
28
+ #
29
+ # @option opts [object, Array] :args zero or more arguments to be passed the task
30
+ # block on execution
31
+ #
32
+ # @raise [ArgumentError] if no block is given
33
+ def initialize(opts = {}, &block)
34
+ raise ArgumentError.new('no block given') unless block_given?
35
+ super(NULL, opts.merge(__task_from_block__: block), &nil)
36
+ end
37
+
38
+ # Execute an `:unscheduled` `Future`. Immediately sets the state to `:pending` and
39
+ # passes the block to a new thread/thread pool for eventual execution.
40
+ # Does nothing if the `Future` is in any state other than `:unscheduled`.
41
+ #
42
+ # @return [Future] a reference to `self`
43
+ #
44
+ # @example Instance and execute in separate steps
45
+ # future = Concurrent::Future.new{ sleep(1); 42 }
46
+ # future.state #=> :unscheduled
47
+ # future.execute
48
+ # future.state #=> :pending
49
+ #
50
+ # @example Instance and execute in one line
51
+ # future = Concurrent::Future.new{ sleep(1); 42 }.execute
52
+ # future.state #=> :pending
53
+ def execute
54
+ if compare_and_set_state(:pending, :unscheduled)
55
+ @executor.post{ safe_execute(@task, @args) }
56
+ self
57
+ end
58
+ end
59
+
60
+ # Create a new `Future` object with the given block, execute it, and return the
61
+ # `:pending` object.
62
+ #
63
+ # @yield the asynchronous operation to perform
64
+ #
65
+ # @!macro executor_and_deref_options
66
+ #
67
+ # @option opts [object, Array] :args zero or more arguments to be passed the task
68
+ # block on execution
69
+ #
70
+ # @raise [ArgumentError] if no block is given
71
+ #
72
+ # @return [Future] the newly created `Future` in the `:pending` state
73
+ #
74
+ # @example
75
+ # future = Concurrent::Future.execute{ sleep(1); 42 }
76
+ # future.state #=> :pending
77
+ def self.execute(opts = {}, &block)
78
+ Future.new(opts, &block).execute
79
+ end
80
+
81
+ # @!macro ivar_set_method
82
+ def set(value = NULL, &block)
83
+ check_for_block_or_value!(block_given?, value)
84
+ synchronize do
85
+ if @state != :unscheduled
86
+ raise MultipleAssignmentError
87
+ else
88
+ @task = block || Proc.new { value }
89
+ end
90
+ end
91
+ execute
92
+ end
93
+
94
+ # Attempt to cancel the operation if it has not already processed.
95
+ # The operation can only be cancelled while still `pending`. It cannot
96
+ # be cancelled once it has begun processing or has completed.
97
+ #
98
+ # @return [Boolean] was the operation successfully cancelled.
99
+ def cancel
100
+ if compare_and_set_state(:cancelled, :pending)
101
+ complete(false, nil, CancelledOperationError.new)
102
+ true
103
+ else
104
+ false
105
+ end
106
+ end
107
+
108
+ # Has the operation been successfully cancelled?
109
+ #
110
+ # @return [Boolean]
111
+ def cancelled?
112
+ state == :cancelled
113
+ end
114
+
115
+ # Wait the given number of seconds for the operation to complete.
116
+ # On timeout attempt to cancel the operation.
117
+ #
118
+ # @param [Numeric] timeout the maximum time in seconds to wait.
119
+ # @return [Boolean] true if the operation completed before the timeout
120
+ # else false
121
+ def wait_or_cancel(timeout)
122
+ wait(timeout)
123
+ if complete?
124
+ true
125
+ else
126
+ cancel
127
+ false
128
+ end
129
+ end
130
+
131
+ protected
132
+
133
+ def ns_initialize(value, opts)
134
+ super
135
+ @state = :unscheduled
136
+ @task = opts[:__task_from_block__]
137
+ @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor
138
+ @args = get_arguments_from(opts)
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,59 @@
1
+ require 'concurrent/utility/engine'
2
+ require 'concurrent/thread_safe/util'
3
+
4
+ module Concurrent
5
+
6
+ # @!macro concurrent_hash
7
+ #
8
+ # A thread-safe subclass of Hash. This version locks against the object
9
+ # itself for every method call, ensuring only one thread can be reading
10
+ # or writing at a time. This includes iteration methods like `#each`,
11
+ # which takes the lock repeatedly when reading an item.
12
+ #
13
+ # @see http://ruby-doc.org/core-2.2.0/Hash.html Ruby standard library `Hash`
14
+
15
+ # @!macro internal_implementation_note
16
+ HashImplementation = case
17
+ when Concurrent.on_cruby?
18
+ # Hash is thread-safe in practice because CRuby runs
19
+ # threads one at a time and does not do context
20
+ # switching during the execution of C functions.
21
+ ::Hash
22
+
23
+ when Concurrent.on_jruby?
24
+ require 'jruby/synchronized'
25
+
26
+ class JRubyHash < ::Hash
27
+ include JRuby::Synchronized
28
+ end
29
+ JRubyHash
30
+
31
+ when Concurrent.on_rbx?
32
+ require 'monitor'
33
+ require 'concurrent/thread_safe/util/data_structures'
34
+
35
+ class RbxHash < ::Hash
36
+ end
37
+ ThreadSafe::Util.make_synchronized_on_rbx RbxHash
38
+ RbxHash
39
+
40
+ when Concurrent.on_truffleruby?
41
+ require 'concurrent/thread_safe/util/data_structures'
42
+
43
+ class TruffleRubyHash < ::Hash
44
+ end
45
+
46
+ ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyHash
47
+ TruffleRubyHash
48
+
49
+ else
50
+ warn 'Possibly unsupported Ruby implementation'
51
+ ::Hash
52
+ end
53
+ private_constant :HashImplementation
54
+
55
+ # @!macro concurrent_hash
56
+ class Hash < HashImplementation
57
+ end
58
+
59
+ end
@@ -0,0 +1,93 @@
1
+ require 'concurrent/synchronization/abstract_struct'
2
+ require 'concurrent/synchronization'
3
+
4
+ module Concurrent
5
+
6
+ # A thread-safe, immutable variation of Ruby's standard `Struct`.
7
+ #
8
+ # @see http://ruby-doc.org/core-2.2.0/Struct.html Ruby standard library `Struct`
9
+ module ImmutableStruct
10
+ include Synchronization::AbstractStruct
11
+
12
+ def self.included(base)
13
+ base.safe_initialization!
14
+ end
15
+
16
+ # @!macro struct_values
17
+ def values
18
+ ns_values
19
+ end
20
+
21
+ alias_method :to_a, :values
22
+
23
+ # @!macro struct_values_at
24
+ def values_at(*indexes)
25
+ ns_values_at(indexes)
26
+ end
27
+
28
+ # @!macro struct_inspect
29
+ def inspect
30
+ ns_inspect
31
+ end
32
+
33
+ alias_method :to_s, :inspect
34
+
35
+ # @!macro struct_merge
36
+ def merge(other, &block)
37
+ ns_merge(other, &block)
38
+ end
39
+
40
+ # @!macro struct_to_h
41
+ def to_h
42
+ ns_to_h
43
+ end
44
+
45
+ # @!macro struct_get
46
+ def [](member)
47
+ ns_get(member)
48
+ end
49
+
50
+ # @!macro struct_equality
51
+ def ==(other)
52
+ ns_equality(other)
53
+ end
54
+
55
+ # @!macro struct_each
56
+ def each(&block)
57
+ return enum_for(:each) unless block_given?
58
+ ns_each(&block)
59
+ end
60
+
61
+ # @!macro struct_each_pair
62
+ def each_pair(&block)
63
+ return enum_for(:each_pair) unless block_given?
64
+ ns_each_pair(&block)
65
+ end
66
+
67
+ # @!macro struct_select
68
+ def select(&block)
69
+ return enum_for(:select) unless block_given?
70
+ ns_select(&block)
71
+ end
72
+
73
+ # @!macro struct_new
74
+ def self.new(*args, &block)
75
+ clazz_name = nil
76
+ if args.length == 0
77
+ raise ArgumentError.new('wrong number of arguments (0 for 1+)')
78
+ elsif args.length > 0 && args.first.is_a?(String)
79
+ clazz_name = args.shift
80
+ end
81
+ FACTORY.define_struct(clazz_name, args, &block)
82
+ end
83
+
84
+ FACTORY = Class.new(Synchronization::LockableObject) do
85
+ def define_struct(name, members, &block)
86
+ synchronize do
87
+ Synchronization::AbstractStruct.define_struct_class(ImmutableStruct, Synchronization::Object, name, members, &block)
88
+ end
89
+ end
90
+ end.new
91
+ private_constant :FACTORY
92
+ end
93
+ end
@@ -0,0 +1,207 @@
1
+ require 'concurrent/constants'
2
+ require 'concurrent/errors'
3
+ require 'concurrent/collection/copy_on_write_observer_set'
4
+ require 'concurrent/concern/obligation'
5
+ require 'concurrent/concern/observable'
6
+ require 'concurrent/synchronization'
7
+
8
+ module Concurrent
9
+
10
+ # An `IVar` is like a future that you can assign. As a future is a value that
11
+ # is being computed that you can wait on, an `IVar` is a value that is waiting
12
+ # to be assigned, that you can wait on. `IVars` are single assignment and
13
+ # deterministic.
14
+ #
15
+ # Then, express futures as an asynchronous computation that assigns an `IVar`.
16
+ # The `IVar` becomes the primitive on which [futures](Future) and
17
+ # [dataflow](Dataflow) are built.
18
+ #
19
+ # An `IVar` is a single-element container that is normally created empty, and
20
+ # can only be set once. The I in `IVar` stands for immutable. Reading an
21
+ # `IVar` normally blocks until it is set. It is safe to set and read an `IVar`
22
+ # from different threads.
23
+ #
24
+ # If you want to have some parallel task set the value in an `IVar`, you want
25
+ # a `Future`. If you want to create a graph of parallel tasks all executed
26
+ # when the values they depend on are ready you want `dataflow`. `IVar` is
27
+ # generally a low-level primitive.
28
+ #
29
+ # ## Examples
30
+ #
31
+ # Create, set and get an `IVar`
32
+ #
33
+ # ```ruby
34
+ # ivar = Concurrent::IVar.new
35
+ # ivar.set 14
36
+ # ivar.value #=> 14
37
+ # ivar.set 2 # would now be an error
38
+ # ```
39
+ #
40
+ # ## See Also
41
+ #
42
+ # 1. For the theory: Arvind, R. Nikhil, and K. Pingali.
43
+ # [I-Structures: Data structures for parallel computing](http://dl.acm.org/citation.cfm?id=69562).
44
+ # In Proceedings of Workshop on Graph Reduction, 1986.
45
+ # 2. For recent application:
46
+ # [DataDrivenFuture in Habanero Java from Rice](http://www.cs.rice.edu/~vs3/hjlib/doc/edu/rice/hj/api/HjDataDrivenFuture.html).
47
+ class IVar < Synchronization::LockableObject
48
+ include Concern::Obligation
49
+ include Concern::Observable
50
+
51
+ # Create a new `IVar` in the `:pending` state with the (optional) initial value.
52
+ #
53
+ # @param [Object] value the initial value
54
+ # @param [Hash] opts the options to create a message with
55
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning
56
+ # the data
57
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before
58
+ # returning the data
59
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
60
+ # the internal value and returning the value returned from the proc
61
+ def initialize(value = NULL, opts = {}, &block)
62
+ if value != NULL && block_given?
63
+ raise ArgumentError.new('provide only a value or a block')
64
+ end
65
+ super(&nil)
66
+ synchronize { ns_initialize(value, opts, &block) }
67
+ end
68
+
69
+ # Add an observer on this object that will receive notification on update.
70
+ #
71
+ # Upon completion the `IVar` will notify all observers in a thread-safe way.
72
+ # The `func` method of the observer will be called with three arguments: the
73
+ # `Time` at which the `Future` completed the asynchronous operation, the
74
+ # final `value` (or `nil` on rejection), and the final `reason` (or `nil` on
75
+ # fulfillment).
76
+ #
77
+ # @param [Object] observer the object that will be notified of changes
78
+ # @param [Symbol] func symbol naming the method to call when this
79
+ # `Observable` has changes`
80
+ def add_observer(observer = nil, func = :update, &block)
81
+ raise ArgumentError.new('cannot provide both an observer and a block') if observer && block
82
+ direct_notification = false
83
+
84
+ if block
85
+ observer = block
86
+ func = :call
87
+ end
88
+
89
+ synchronize do
90
+ if event.set?
91
+ direct_notification = true
92
+ else
93
+ observers.add_observer(observer, func)
94
+ end
95
+ end
96
+
97
+ observer.send(func, Time.now, self.value, reason) if direct_notification
98
+ observer
99
+ end
100
+
101
+ # @!macro ivar_set_method
102
+ # Set the `IVar` to a value and wake or notify all threads waiting on it.
103
+ #
104
+ # @!macro ivar_set_parameters_and_exceptions
105
+ # @param [Object] value the value to store in the `IVar`
106
+ # @yield A block operation to use for setting the value
107
+ # @raise [ArgumentError] if both a value and a block are given
108
+ # @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already
109
+ # been set or otherwise completed
110
+ #
111
+ # @return [IVar] self
112
+ def set(value = NULL)
113
+ check_for_block_or_value!(block_given?, value)
114
+ raise MultipleAssignmentError unless compare_and_set_state(:processing, :pending)
115
+
116
+ begin
117
+ value = yield if block_given?
118
+ complete_without_notification(true, value, nil)
119
+ rescue => ex
120
+ complete_without_notification(false, nil, ex)
121
+ end
122
+
123
+ notify_observers(self.value, reason)
124
+ self
125
+ end
126
+
127
+ # @!macro ivar_fail_method
128
+ # Set the `IVar` to failed due to some error and wake or notify all threads waiting on it.
129
+ #
130
+ # @param [Object] reason for the failure
131
+ # @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already
132
+ # been set or otherwise completed
133
+ # @return [IVar] self
134
+ def fail(reason = StandardError.new)
135
+ complete(false, nil, reason)
136
+ end
137
+
138
+ # Attempt to set the `IVar` with the given value or block. Return a
139
+ # boolean indicating the success or failure of the set operation.
140
+ #
141
+ # @!macro ivar_set_parameters_and_exceptions
142
+ #
143
+ # @return [Boolean] true if the value was set else false
144
+ def try_set(value = NULL, &block)
145
+ set(value, &block)
146
+ true
147
+ rescue MultipleAssignmentError
148
+ false
149
+ end
150
+
151
+ protected
152
+
153
+ # @!visibility private
154
+ def ns_initialize(value, opts)
155
+ value = yield if block_given?
156
+ init_obligation
157
+ self.observers = Collection::CopyOnWriteObserverSet.new
158
+ set_deref_options(opts)
159
+
160
+ @state = :pending
161
+ if value != NULL
162
+ ns_complete_without_notification(true, value, nil)
163
+ end
164
+ end
165
+
166
+ # @!visibility private
167
+ def safe_execute(task, args = [])
168
+ if compare_and_set_state(:processing, :pending)
169
+ success, val, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args)
170
+ complete(success, val, reason)
171
+ yield(success, val, reason) if block_given?
172
+ end
173
+ end
174
+
175
+ # @!visibility private
176
+ def complete(success, value, reason)
177
+ complete_without_notification(success, value, reason)
178
+ notify_observers(self.value, reason)
179
+ self
180
+ end
181
+
182
+ # @!visibility private
183
+ def complete_without_notification(success, value, reason)
184
+ synchronize { ns_complete_without_notification(success, value, reason) }
185
+ self
186
+ end
187
+
188
+ # @!visibility private
189
+ def notify_observers(value, reason)
190
+ observers.notify_and_delete_observers{ [Time.now, value, reason] }
191
+ end
192
+
193
+ # @!visibility private
194
+ def ns_complete_without_notification(success, value, reason)
195
+ raise MultipleAssignmentError if [:fulfilled, :rejected].include? @state
196
+ set_state(success, value, reason)
197
+ event.set
198
+ end
199
+
200
+ # @!visibility private
201
+ def check_for_block_or_value!(block_given, value) # :nodoc:
202
+ if (block_given && value != NULL) || (! block_given && value == NULL)
203
+ raise ArgumentError.new('must set with either a value or a block')
204
+ end
205
+ end
206
+ end
207
+ end