concurrent-ruby 0.7.0-java

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 (112) hide show
  1. data/LICENSE.txt +21 -0
  2. data/README.md +217 -0
  3. data/lib/concurrent.rb +45 -0
  4. data/lib/concurrent/actor.rb +104 -0
  5. data/lib/concurrent/actor/behaviour.rb +70 -0
  6. data/lib/concurrent/actor/behaviour/abstract.rb +48 -0
  7. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  8. data/lib/concurrent/actor/behaviour/buffer.rb +54 -0
  9. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  10. data/lib/concurrent/actor/behaviour/executes_context.rb +18 -0
  11. data/lib/concurrent/actor/behaviour/linking.rb +42 -0
  12. data/lib/concurrent/actor/behaviour/pausing.rb +77 -0
  13. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  14. data/lib/concurrent/actor/behaviour/sets_results.rb +36 -0
  15. data/lib/concurrent/actor/behaviour/supervised.rb +58 -0
  16. data/lib/concurrent/actor/behaviour/supervising.rb +34 -0
  17. data/lib/concurrent/actor/behaviour/terminates_children.rb +13 -0
  18. data/lib/concurrent/actor/behaviour/termination.rb +54 -0
  19. data/lib/concurrent/actor/context.rb +153 -0
  20. data/lib/concurrent/actor/core.rb +213 -0
  21. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  22. data/lib/concurrent/actor/envelope.rb +41 -0
  23. data/lib/concurrent/actor/errors.rb +27 -0
  24. data/lib/concurrent/actor/internal_delegations.rb +49 -0
  25. data/lib/concurrent/actor/public_delegations.rb +40 -0
  26. data/lib/concurrent/actor/reference.rb +81 -0
  27. data/lib/concurrent/actor/root.rb +37 -0
  28. data/lib/concurrent/actor/type_check.rb +48 -0
  29. data/lib/concurrent/actor/utils.rb +10 -0
  30. data/lib/concurrent/actor/utils/ad_hoc.rb +21 -0
  31. data/lib/concurrent/actor/utils/balancer.rb +40 -0
  32. data/lib/concurrent/actor/utils/broadcast.rb +52 -0
  33. data/lib/concurrent/actor/utils/pool.rb +59 -0
  34. data/lib/concurrent/actress.rb +3 -0
  35. data/lib/concurrent/agent.rb +230 -0
  36. data/lib/concurrent/async.rb +284 -0
  37. data/lib/concurrent/atomic.rb +91 -0
  38. data/lib/concurrent/atomic/atomic_boolean.rb +202 -0
  39. data/lib/concurrent/atomic/atomic_fixnum.rb +203 -0
  40. data/lib/concurrent/atomic/condition.rb +67 -0
  41. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
  42. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
  43. data/lib/concurrent/atomic/count_down_latch.rb +116 -0
  44. data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
  45. data/lib/concurrent/atomic/event.rb +98 -0
  46. data/lib/concurrent/atomic/synchronization.rb +51 -0
  47. data/lib/concurrent/atomic/thread_local_var.rb +82 -0
  48. data/lib/concurrent/atomic_reference/concurrent_update_error.rb +8 -0
  49. data/lib/concurrent/atomic_reference/direct_update.rb +50 -0
  50. data/lib/concurrent/atomic_reference/jruby.rb +14 -0
  51. data/lib/concurrent/atomic_reference/mutex_atomic.rb +77 -0
  52. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +25 -0
  53. data/lib/concurrent/atomic_reference/rbx.rb +19 -0
  54. data/lib/concurrent/atomic_reference/ruby.rb +37 -0
  55. data/lib/concurrent/atomics.rb +11 -0
  56. data/lib/concurrent/channel/buffered_channel.rb +85 -0
  57. data/lib/concurrent/channel/channel.rb +41 -0
  58. data/lib/concurrent/channel/unbuffered_channel.rb +35 -0
  59. data/lib/concurrent/channel/waitable_list.rb +40 -0
  60. data/lib/concurrent/channels.rb +5 -0
  61. data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
  62. data/lib/concurrent/collection/priority_queue.rb +305 -0
  63. data/lib/concurrent/collection/ring_buffer.rb +59 -0
  64. data/lib/concurrent/collections.rb +3 -0
  65. data/lib/concurrent/configuration.rb +161 -0
  66. data/lib/concurrent/dataflow.rb +108 -0
  67. data/lib/concurrent/delay.rb +104 -0
  68. data/lib/concurrent/dereferenceable.rb +101 -0
  69. data/lib/concurrent/errors.rb +30 -0
  70. data/lib/concurrent/exchanger.rb +34 -0
  71. data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
  72. data/lib/concurrent/executor/executor.rb +282 -0
  73. data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
  74. data/lib/concurrent/executor/immediate_executor.rb +65 -0
  75. data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
  76. data/lib/concurrent/executor/java_fixed_thread_pool.rb +41 -0
  77. data/lib/concurrent/executor/java_single_thread_executor.rb +22 -0
  78. data/lib/concurrent/executor/java_thread_pool_executor.rb +180 -0
  79. data/lib/concurrent/executor/per_thread_executor.rb +100 -0
  80. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
  81. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
  82. data/lib/concurrent/executor/ruby_single_thread_executor.rb +74 -0
  83. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +288 -0
  84. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
  85. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  86. data/lib/concurrent/executor/serialized_execution.rb +126 -0
  87. data/lib/concurrent/executor/single_thread_executor.rb +35 -0
  88. data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
  89. data/lib/concurrent/executor/timer_set.rb +143 -0
  90. data/lib/concurrent/executors.rb +9 -0
  91. data/lib/concurrent/future.rb +125 -0
  92. data/lib/concurrent/ivar.rb +111 -0
  93. data/lib/concurrent/lazy_register.rb +58 -0
  94. data/lib/concurrent/logging.rb +17 -0
  95. data/lib/concurrent/mvar.rb +200 -0
  96. data/lib/concurrent/obligation.rb +171 -0
  97. data/lib/concurrent/observable.rb +40 -0
  98. data/lib/concurrent/options_parser.rb +48 -0
  99. data/lib/concurrent/promise.rb +170 -0
  100. data/lib/concurrent/scheduled_task.rb +79 -0
  101. data/lib/concurrent/timer_task.rb +341 -0
  102. data/lib/concurrent/tvar.rb +248 -0
  103. data/lib/concurrent/utilities.rb +3 -0
  104. data/lib/concurrent/utility/processor_count.rb +152 -0
  105. data/lib/concurrent/utility/timeout.rb +35 -0
  106. data/lib/concurrent/utility/timer.rb +21 -0
  107. data/lib/concurrent/version.rb +3 -0
  108. data/lib/concurrent_ruby.rb +1 -0
  109. data/lib/concurrent_ruby_ext.jar +0 -0
  110. data/lib/concurrent_ruby_ext.so +0 -0
  111. data/lib/extension_helper.rb +28 -0
  112. metadata +163 -0
@@ -0,0 +1,3 @@
1
+ require 'concurrent/actor'
2
+
3
+ Concurrent::Actress = Concurrent::Actor
@@ -0,0 +1,230 @@
1
+ require 'thread'
2
+
3
+ require 'concurrent/dereferenceable'
4
+ require 'concurrent/observable'
5
+ require 'concurrent/options_parser'
6
+ require 'concurrent/utility/timeout'
7
+ require 'concurrent/logging'
8
+
9
+ module Concurrent
10
+
11
+ # An agent is a single atomic value that represents an identity. The current value
12
+ # of the agent can be requested at any time (`#deref`). Each agent has a work queue and operates on
13
+ # the global thread pool. Consumers can `#post` code blocks to the agent. The code block (function)
14
+ # will receive the current value of the agent as its sole parameter. The return value of the block
15
+ # will become the new value of the agent. Agents support two error handling modes: fail and continue.
16
+ # A good example of an agent is a shared incrementing counter, such as the score in a video game.
17
+ #
18
+ # @example Basic usage
19
+ # score = Concurrent::Agent.new(10)
20
+ # score.value #=> 10
21
+ #
22
+ # score << proc{|current| current + 100 }
23
+ # sleep(0.1)
24
+ # score.value #=> 110
25
+ #
26
+ # score << proc{|current| current * 2 }
27
+ # sleep(0.1)
28
+ # score.value #=> 220
29
+ #
30
+ # score << proc{|current| current - 50 }
31
+ # sleep(0.1)
32
+ # score.value #=> 170
33
+ #
34
+ # @!attribute [r] timeout
35
+ # @return [Fixnum] the maximum number of seconds before an update is cancelled
36
+ class Agent
37
+ include Dereferenceable
38
+ include Concurrent::Observable
39
+ include Logging
40
+
41
+ attr_reader :timeout, :task_executor, :operation_executor
42
+
43
+ # Initialize a new Agent with the given initial value and provided options.
44
+ #
45
+ # @param [Object] initial the initial value
46
+ # @param [Hash] opts the options used to define the behavior at update and deref
47
+ #
48
+ # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
49
+ # operation pool (for long-running operations), when `false` will execute the future on the
50
+ # global task pool (for short-running tasks)
51
+ # @option opts [object] :executor when provided will run all operations on
52
+ # this executor rather than the global thread pool (overrides :operation)
53
+ #
54
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
55
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
56
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
57
+ # returning the value returned from the proc
58
+ def initialize(initial, opts = {})
59
+ @value = initial
60
+ @rescuers = []
61
+ @validator = Proc.new { |result| true }
62
+ self.observers = CopyOnWriteObserverSet.new
63
+ @serialized_execution = SerializedExecution.new
64
+ @task_executor = OptionsParser.get_task_executor_from(opts)
65
+ @operation_executor = OptionsParser.get_operation_executor_from(opts)
66
+ init_mutex
67
+ set_deref_options(opts)
68
+ end
69
+
70
+ # Specifies a block operation to be performed when an update operation raises
71
+ # an exception. Rescue blocks will be checked in order they were added. The first
72
+ # block for which the raised exception "is-a" subclass of the given `clazz` will
73
+ # be called. If no `clazz` is given the block will match any caught exception.
74
+ # This behavior is intended to be identical to Ruby's `begin/rescue/end` behavior.
75
+ # Any number of rescue handlers can be added. If no rescue handlers are added then
76
+ # caught exceptions will be suppressed.
77
+ #
78
+ # @param [Exception] clazz the class of exception to catch
79
+ # @yield the block to be called when a matching exception is caught
80
+ # @yieldparam [StandardError] ex the caught exception
81
+ #
82
+ # @example
83
+ # score = Concurrent::Agent.new(0).
84
+ # rescue(NoMethodError){|ex| puts "Bam!" }.
85
+ # rescue(ArgumentError){|ex| puts "Pow!" }.
86
+ # rescue{|ex| puts "Boom!" }
87
+ #
88
+ # score << proc{|current| raise ArgumentError }
89
+ # sleep(0.1)
90
+ # #=> puts "Pow!"
91
+ def rescue(clazz = StandardError, &block)
92
+ unless block.nil?
93
+ mutex.synchronize do
94
+ @rescuers << Rescuer.new(clazz, block)
95
+ end
96
+ end
97
+ self
98
+ end
99
+ alias_method :catch, :rescue
100
+ alias_method :on_error, :rescue
101
+
102
+ # A block operation to be performed after every update to validate if the new
103
+ # value is valid. If the new value is not valid then the current value is not
104
+ # updated. If no validator is provided then all updates are considered valid.
105
+ #
106
+ # @yield the block to be called after every update operation to determine if
107
+ # the result is valid
108
+ # @yieldparam [Object] value the result of the last update operation
109
+ # @yieldreturn [Boolean] true if the value is valid else false
110
+ def validate(&block)
111
+
112
+ unless block.nil?
113
+ begin
114
+ mutex.lock
115
+ @validator = block
116
+ ensure
117
+ mutex.unlock
118
+ end
119
+ end
120
+ self
121
+ end
122
+ alias_method :validates, :validate
123
+ alias_method :validate_with, :validate
124
+ alias_method :validates_with, :validate
125
+
126
+ # Update the current value with the result of the given block operation,
127
+ # block should not do blocking calls, use #post_off for blocking calls
128
+ #
129
+ # @yield the operation to be performed with the current value in order to calculate
130
+ # the new value
131
+ # @yieldparam [Object] value the current value
132
+ # @yieldreturn [Object] the new value
133
+ # @return [true, nil] nil when no block is given
134
+ def post(&block)
135
+ post_on(@task_executor, &block)
136
+ end
137
+
138
+ # Update the current value with the result of the given block operation,
139
+ # block can do blocking calls
140
+ #
141
+ # @param [Fixnum, nil] timeout maximum number of seconds before an update is cancelled
142
+ #
143
+ # @yield the operation to be performed with the current value in order to calculate
144
+ # the new value
145
+ # @yieldparam [Object] value the current value
146
+ # @yieldreturn [Object] the new value
147
+ # @return [true, nil] nil when no block is given
148
+ def post_off(timeout = nil, &block)
149
+ block = if timeout
150
+ lambda { |value| Concurrent::timeout(timeout) { block.call(value) } }
151
+ else
152
+ block
153
+ end
154
+ post_on(@operation_executor, &block)
155
+ end
156
+
157
+ # Update the current value with the result of the given block operation,
158
+ # block should not do blocking calls, use #post_off for blocking calls
159
+ #
160
+ # @yield the operation to be performed with the current value in order to calculate
161
+ # the new value
162
+ # @yieldparam [Object] value the current value
163
+ # @yieldreturn [Object] the new value
164
+ def <<(block)
165
+ post(&block)
166
+ self
167
+ end
168
+
169
+ # Waits/blocks until all the updates sent before this call are done.
170
+ #
171
+ # @param [Numeric] timeout the maximum time in second to wait.
172
+ # @return [Boolean] false on timeout, true otherwise
173
+ def await(timeout = nil)
174
+ done = Event.new
175
+ post { |val| done.set; val }
176
+ done.wait timeout
177
+ end
178
+
179
+ private
180
+
181
+ def post_on(executor, &block)
182
+ return nil if block.nil?
183
+ @serialized_execution.post(executor) { work(&block) }
184
+ true
185
+ end
186
+
187
+ # @!visibility private
188
+ Rescuer = Struct.new(:clazz, :block) # :nodoc:
189
+
190
+ # @!visibility private
191
+ def try_rescue(ex) # :nodoc:
192
+ rescuer = mutex.synchronize do
193
+ @rescuers.find { |r| ex.is_a?(r.clazz) }
194
+ end
195
+ rescuer.block.call(ex) if rescuer
196
+ rescue Exception => ex
197
+ # suppress
198
+ log DEBUG, ex
199
+ end
200
+
201
+ # @!visibility private
202
+ def work(&handler) # :nodoc:
203
+ validator, value = mutex.synchronize { [@validator, @value] }
204
+
205
+ begin
206
+ result = handler.call(value)
207
+ valid = validator.call(result)
208
+ rescue Exception => ex
209
+ exception = ex
210
+ end
211
+
212
+ begin
213
+ mutex.lock
214
+ should_notify = if !exception && valid
215
+ @value = result
216
+ true
217
+ end
218
+ ensure
219
+ mutex.unlock
220
+ end
221
+
222
+ if should_notify
223
+ time = Time.now
224
+ observers.notify_observers { [time, self.value] }
225
+ end
226
+
227
+ try_rescue(exception)
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,284 @@
1
+ require 'thread'
2
+ require 'concurrent/configuration'
3
+ require 'concurrent/delay'
4
+ require 'concurrent/errors'
5
+ require 'concurrent/ivar'
6
+ require 'concurrent/executor/immediate_executor'
7
+ require 'concurrent/executor/serialized_execution'
8
+
9
+ module Concurrent
10
+
11
+ # A mixin module that provides simple asynchronous behavior to any standard
12
+ # class/object or object.
13
+ #
14
+ # Scenario:
15
+ # As a stateful, plain old Ruby class/object
16
+ # I want safe, asynchronous behavior
17
+ # So my long-running methods don't block the main thread
18
+ #
19
+ # Stateful, mutable objects must be managed carefully when used asynchronously.
20
+ # But Ruby is an object-oriented language so designing with objects and classes
21
+ # plays to Ruby's strengths and is often more natural to many Ruby programmers.
22
+ # The `Async` module is a way to mix simple yet powerful asynchronous capabilities
23
+ # into any plain old Ruby object or class. These capabilities provide a reasonable
24
+ # level of thread safe guarantees when used correctly.
25
+ #
26
+ # When this module is mixed into a class or object it provides to new methods:
27
+ # `async` and `await`. These methods are thread safe with respect to the enclosing
28
+ # object. The former method allows methods to be called asynchronously by posting
29
+ # to the global thread pool. The latter allows a method to be called synchronously
30
+ # on the current thread but does so safely with respect to any pending asynchronous
31
+ # method calls. Both methods return an `Obligation` which can be inspected for
32
+ # the result of the method call. Calling a method with `async` will return a
33
+ # `:pending` `Obligation` whereas `await` will return a `:complete` `Obligation`.
34
+ #
35
+ # Very loosely based on the `async` and `await` keywords in C#.
36
+ #
37
+ # @example Defining an asynchronous class
38
+ # class Echo
39
+ # include Concurrent::Async
40
+ #
41
+ # def initialize
42
+ # init_mutex # initialize the internal synchronization objects
43
+ # end
44
+ #
45
+ # def echo(msg)
46
+ # sleep(rand)
47
+ # print "#{msg}\n"
48
+ # nil
49
+ # end
50
+ # end
51
+ #
52
+ # horn = Echo.new
53
+ # horn.echo('zero') # synchronous, not thread-safe
54
+ #
55
+ # horn.async.echo('one') # asynchronous, non-blocking, thread-safe
56
+ # horn.await.echo('two') # synchronous, blocking, thread-safe
57
+ #
58
+ # @example Monkey-patching an existing object
59
+ # numbers = 1_000_000.times.collect{ rand }
60
+ # numbers.extend(Concurrent::Async)
61
+ # numbers.init_mutex # initialize the internal synchronization objects
62
+ #
63
+ # future = numbers.async.max
64
+ # future.state #=> :pending
65
+ #
66
+ # sleep(2)
67
+ #
68
+ # future.state #=> :fulfilled
69
+ # future.value #=> 0.999999138918843
70
+ #
71
+ # @note This module depends on several internal synchronization objects that
72
+ # must be initialized prior to calling any of the async/await/executor methods.
73
+ # The best practice is to call `init_mutex` from within the constructor
74
+ # of the including class. A less ideal but acceptable practice is for the
75
+ # thread creating the asynchronous object to explicitly call the `init_mutex`
76
+ # method prior to calling any of the async/await/executor methods. If
77
+ # `init_mutex` is *not* called explicitly the async/await/executor methods
78
+ # will raize a `Concurrent::InitializationError`. This is the only way
79
+ # thread-safe initialization can be guaranteed.
80
+ #
81
+ # @note Thread safe guarantees can only be made when asynchronous method calls
82
+ # are not mixed with synchronous method calls. Use only synchronous calls
83
+ # when the object is used exclusively on a single thread. Use only
84
+ # `async` and `await` when the object is shared between threads. Once you
85
+ # call a method using `async`, you should no longer call any methods
86
+ # directly on the object. Use `async` and `await` exclusively from then on.
87
+ # With careful programming it is possible to switch back and forth but it's
88
+ # also very easy to create race conditions and break your application.
89
+ # Basically, it's "async all the way down."
90
+ #
91
+ # @since 0.6.0
92
+ #
93
+ # @see Concurrent::Obligation
94
+ module Async
95
+
96
+ # Check for the presence of a method on an object and determine if a given
97
+ # set of arguments matches the required arity.
98
+ #
99
+ # @param [Object] obj the object to check against
100
+ # @param [Symbol] method the method to check the object for
101
+ # @param [Array] args zero or more arguments for the arity check
102
+ #
103
+ # @raise [NameError] the object does not respond to `method` method
104
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
105
+ #
106
+ # @note This check is imperfect because of the way Ruby reports the arity of
107
+ # methods with a variable number of arguments. It is possible to determine
108
+ # if too few arguments are given but impossible to determine if too many
109
+ # arguments are given. This check may also fail to recognize dynamic behavior
110
+ # of the object, such as methods simulated with `method_missing`.
111
+ #
112
+ # @see http://www.ruby-doc.org/core-2.1.1/Method.html#method-i-arity Method#arity
113
+ # @see http://ruby-doc.org/core-2.1.0/Object.html#method-i-respond_to-3F Object#respond_to?
114
+ # @see http://www.ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing BasicObject#method_missing
115
+ def validate_argc(obj, method, *args)
116
+ argc = args.length
117
+ arity = obj.method(method).arity
118
+
119
+ if arity >= 0 && argc != arity
120
+ raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity})")
121
+ elsif arity < 0 && (arity = (arity + 1).abs) > argc
122
+ raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity}..*)")
123
+ end
124
+ end
125
+ module_function :validate_argc
126
+
127
+ # Delegates asynchronous, thread-safe method calls to the wrapped object.
128
+ #
129
+ # @!visibility private
130
+ class AsyncDelegator # :nodoc:
131
+
132
+ # Create a new delegator object wrapping the given delegate,
133
+ # protecting it with the given serializer, and executing it on the
134
+ # given executor. Block if necessary.
135
+ #
136
+ # @param [Object] delegate the object to wrap and delegate method calls to
137
+ # @param [Concurrent::Delay] executor a `Delay` wrapping the executor on which to execute delegated method calls
138
+ # @param [Concurrent::SerializedExecution] serializer the serializer to use when delegating method calls
139
+ # @param [Boolean] blocking will block awaiting result when `true`
140
+ def initialize(delegate, executor, serializer, blocking = false)
141
+ @delegate = delegate
142
+ @executor = executor
143
+ @serializer = serializer
144
+ @blocking = blocking
145
+ end
146
+
147
+ # Delegates method calls to the wrapped object. For performance,
148
+ # dynamically defines the given method on the delegator so that
149
+ # all future calls to `method` will not be directed here.
150
+ #
151
+ # @param [Symbol] method the method being called
152
+ # @param [Array] args zero or more arguments to the method
153
+ #
154
+ # @return [IVar] the result of the method call
155
+ #
156
+ # @raise [NameError] the object does not respond to `method` method
157
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
158
+ def method_missing(method, *args, &block)
159
+ super unless @delegate.respond_to?(method)
160
+ Async::validate_argc(@delegate, method, *args)
161
+
162
+ self.define_singleton_method(method) do |*args|
163
+ Async::validate_argc(@delegate, method, *args)
164
+ ivar = Concurrent::IVar.new
165
+ value, reason = nil, nil
166
+ @serializer.post(@executor.value) do
167
+ begin
168
+ value = @delegate.send(method, *args, &block)
169
+ rescue => reason
170
+ # caught
171
+ ensure
172
+ ivar.complete(reason.nil?, value, reason)
173
+ end
174
+ end
175
+ ivar.value if @blocking
176
+ ivar
177
+ end
178
+
179
+ self.send(method, *args)
180
+ end
181
+ end
182
+
183
+ # Causes the chained method call to be performed asynchronously on the
184
+ # global thread pool. The method called by this method will return a
185
+ # future object in the `:pending` state and the method call will have
186
+ # been scheduled on the global thread pool. The final disposition of the
187
+ # method call can be obtained by inspecting the returned future.
188
+ #
189
+ # Before scheduling the method on the global thread pool a best-effort
190
+ # attempt will be made to validate that the method exists on the object
191
+ # and that the given arguments match the arity of the requested function.
192
+ # Due to the dynamic nature of Ruby and limitations of its reflection
193
+ # library, some edge cases will be missed. For more information see
194
+ # the documentation for the `validate_argc` method.
195
+ #
196
+ # @note The method call is guaranteed to be thread safe with respect to
197
+ # all other method calls against the same object that are called with
198
+ # either `async` or `await`. The mutable nature of Ruby references
199
+ # (and object orientation in general) prevent any other thread safety
200
+ # guarantees. Do NOT mix non-protected method calls with protected
201
+ # method call. Use *only* protected method calls when sharing the object
202
+ # between threads.
203
+ #
204
+ # @return [Concurrent::IVar] the pending result of the asynchronous operation
205
+ #
206
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
207
+ # @raise [NameError] the object does not respond to `method` method
208
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
209
+ #
210
+ # @see Concurrent::IVar
211
+ def async
212
+ raise InitializationError.new('#init_mutex was never called') unless @__async_initialized__
213
+ @__async_delegator__.value
214
+ end
215
+ alias_method :future, :async
216
+
217
+ # Causes the chained method call to be performed synchronously on the
218
+ # current thread. The method called by this method will return an
219
+ # `IVar` object in either the `:fulfilled` or `rejected` state and the
220
+ # method call will have completed. The final disposition of the
221
+ # method call can be obtained by inspecting the returned `IVar`.
222
+ #
223
+ # Before scheduling the method on the global thread pool a best-effort
224
+ # attempt will be made to validate that the method exists on the object
225
+ # and that the given arguments match the arity of the requested function.
226
+ # Due to the dynamic nature of Ruby and limitations of its reflection
227
+ # library, some edge cases will be missed. For more information see
228
+ # the documentation for the `validate_argc` method.
229
+ #
230
+ # @note The method call is guaranteed to be thread safe with respect to
231
+ # all other method calls against the same object that are called with
232
+ # either `async` or `await`. The mutable nature of Ruby references
233
+ # (and object orientation in general) prevent any other thread safety
234
+ # guarantees. Do NOT mix non-protected method calls with protected
235
+ # method call. Use *only* protected method calls when sharing the object
236
+ # between threads.
237
+ #
238
+ # @return [Concurrent::IVar] the completed result of the synchronous operation
239
+ #
240
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
241
+ # @raise [NameError] the object does not respond to `method` method
242
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
243
+ #
244
+ # @see Concurrent::IVar
245
+ def await
246
+ raise InitializationError.new('#init_mutex was never called') unless @__async_initialized__
247
+ @__await_delegator__.value
248
+ end
249
+ alias_method :delay, :await
250
+
251
+ # Set a new executor
252
+ #
253
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
254
+ # @raise [ArgumentError] executor has already been set
255
+ def executor=(executor)
256
+ raise InitializationError.new('#init_mutex was never called') unless @__async_initialized__
257
+ @__async_executor__.reconfigure { executor } or
258
+ raise ArgumentError.new('executor has already been set')
259
+ end
260
+
261
+ # Initialize the internal serializer and other synchronization objects. This method
262
+ # *must* be called from the constructor of the including class or explicitly
263
+ # by the caller prior to calling any other methods. If `init_mutex` is *not*
264
+ # called explicitly the async/await/executor methods will raize a
265
+ # `Concurrent::InitializationError`. This is the only way thread-safe
266
+ # initialization can be guaranteed.
267
+ #
268
+ # @note This method *must* be called from the constructor of the including
269
+ # class or explicitly by the caller prior to calling any other methods.
270
+ # This is the only way thread-safe initialization can be guaranteed.
271
+ #
272
+ # @raise [Concurrent::InitializationError] when called more than once
273
+ def init_mutex
274
+ raise InitializationError.new('#init_mutex was already called') if @__async_initialized__
275
+ @__async_initialized__ = true
276
+ serializer = Concurrent::SerializedExecution.new
277
+ @__async_executor__ = Delay.new{ Concurrent.configuration.global_operation_pool }
278
+ @__await_delegator__ = Delay.new{ AsyncDelegator.new(
279
+ self, Delay.new{ Concurrent::ImmediateExecutor.new }, serializer, true) }
280
+ @__async_delegator__ = Delay.new{ AsyncDelegator.new(
281
+ self, @__async_executor__, serializer, false) }
282
+ end
283
+ end
284
+ end