concurrent-ruby 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
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