concurrent-ruby 0.7.0.rc0-x86-linux

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 (94) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +166 -0
  4. data/ext/concurrent_ruby_ext/atomic_reference.c +78 -0
  5. data/ext/concurrent_ruby_ext/atomic_reference.h +12 -0
  6. data/ext/concurrent_ruby_ext/extconf.rb +59 -0
  7. data/ext/concurrent_ruby_ext/rb_concurrent.c +28 -0
  8. data/lib/concurrent.rb +45 -0
  9. data/lib/concurrent/actress.rb +221 -0
  10. data/lib/concurrent/actress/ad_hoc.rb +20 -0
  11. data/lib/concurrent/actress/context.rb +98 -0
  12. data/lib/concurrent/actress/core.rb +228 -0
  13. data/lib/concurrent/actress/core_delegations.rb +42 -0
  14. data/lib/concurrent/actress/envelope.rb +41 -0
  15. data/lib/concurrent/actress/errors.rb +14 -0
  16. data/lib/concurrent/actress/reference.rb +64 -0
  17. data/lib/concurrent/actress/type_check.rb +48 -0
  18. data/lib/concurrent/agent.rb +232 -0
  19. data/lib/concurrent/async.rb +319 -0
  20. data/lib/concurrent/atomic.rb +46 -0
  21. data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
  22. data/lib/concurrent/atomic/atomic_fixnum.rb +162 -0
  23. data/lib/concurrent/atomic/condition.rb +67 -0
  24. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
  25. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
  26. data/lib/concurrent/atomic/count_down_latch.rb +116 -0
  27. data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
  28. data/lib/concurrent/atomic/event.rb +98 -0
  29. data/lib/concurrent/atomic/thread_local_var.rb +117 -0
  30. data/lib/concurrent/atomic_reference/concurrent_update_error.rb +7 -0
  31. data/lib/concurrent/atomic_reference/delegated_update.rb +28 -0
  32. data/lib/concurrent/atomic_reference/direct_update.rb +28 -0
  33. data/lib/concurrent/atomic_reference/jruby.rb +8 -0
  34. data/lib/concurrent/atomic_reference/mutex_atomic.rb +47 -0
  35. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +24 -0
  36. data/lib/concurrent/atomic_reference/rbx.rb +16 -0
  37. data/lib/concurrent/atomic_reference/ruby.rb +16 -0
  38. data/lib/concurrent/atomics.rb +10 -0
  39. data/lib/concurrent/channel/buffered_channel.rb +85 -0
  40. data/lib/concurrent/channel/channel.rb +41 -0
  41. data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
  42. data/lib/concurrent/channel/waitable_list.rb +40 -0
  43. data/lib/concurrent/channels.rb +5 -0
  44. data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
  45. data/lib/concurrent/collection/priority_queue.rb +305 -0
  46. data/lib/concurrent/collection/ring_buffer.rb +59 -0
  47. data/lib/concurrent/collections.rb +3 -0
  48. data/lib/concurrent/configuration.rb +158 -0
  49. data/lib/concurrent/dataflow.rb +91 -0
  50. data/lib/concurrent/delay.rb +112 -0
  51. data/lib/concurrent/dereferenceable.rb +101 -0
  52. data/lib/concurrent/errors.rb +30 -0
  53. data/lib/concurrent/exchanger.rb +34 -0
  54. data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
  55. data/lib/concurrent/executor/executor.rb +229 -0
  56. data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
  57. data/lib/concurrent/executor/immediate_executor.rb +16 -0
  58. data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
  59. data/lib/concurrent/executor/java_fixed_thread_pool.rb +33 -0
  60. data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
  61. data/lib/concurrent/executor/java_thread_pool_executor.rb +187 -0
  62. data/lib/concurrent/executor/per_thread_executor.rb +24 -0
  63. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
  64. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
  65. data/lib/concurrent/executor/ruby_single_thread_executor.rb +73 -0
  66. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +286 -0
  67. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
  68. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  69. data/lib/concurrent/executor/serialized_execution.rb +90 -0
  70. data/lib/concurrent/executor/single_thread_executor.rb +35 -0
  71. data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
  72. data/lib/concurrent/executor/timer_set.rb +143 -0
  73. data/lib/concurrent/executors.rb +9 -0
  74. data/lib/concurrent/future.rb +124 -0
  75. data/lib/concurrent/ivar.rb +111 -0
  76. data/lib/concurrent/logging.rb +17 -0
  77. data/lib/concurrent/mvar.rb +200 -0
  78. data/lib/concurrent/obligation.rb +171 -0
  79. data/lib/concurrent/observable.rb +40 -0
  80. data/lib/concurrent/options_parser.rb +46 -0
  81. data/lib/concurrent/promise.rb +169 -0
  82. data/lib/concurrent/scheduled_task.rb +78 -0
  83. data/lib/concurrent/supervisor.rb +343 -0
  84. data/lib/concurrent/timer_task.rb +341 -0
  85. data/lib/concurrent/tvar.rb +252 -0
  86. data/lib/concurrent/utilities.rb +3 -0
  87. data/lib/concurrent/utility/processor_count.rb +150 -0
  88. data/lib/concurrent/utility/timeout.rb +35 -0
  89. data/lib/concurrent/utility/timer.rb +21 -0
  90. data/lib/concurrent/version.rb +3 -0
  91. data/lib/concurrent_ruby.rb +1 -0
  92. data/lib/concurrent_ruby_ext.so +0 -0
  93. data/lib/extension_helper.rb +9 -0
  94. metadata +140 -0
@@ -0,0 +1,64 @@
1
+ module Concurrent
2
+ module Actress
3
+
4
+ # Reference is public interface of Actor instances. It is used for sending messages and can
5
+ # be freely passed around the program. It also provides some basic information about the actor,
6
+ # see {CoreDelegations}.
7
+ class Reference
8
+ include TypeCheck
9
+ include CoreDelegations
10
+
11
+ attr_reader :core
12
+ private :core
13
+
14
+ # @!visibility private
15
+ def initialize(core)
16
+ @core = Type! core, Core
17
+ end
18
+
19
+ # tells message to the actor
20
+ # @param [Object] message
21
+ # @return [Reference] self
22
+ def tell(message)
23
+ message message, nil
24
+ end
25
+
26
+ alias_method :<<, :tell
27
+
28
+ # tells message to the actor
29
+ # @param [Object] message
30
+ # @param [Ivar] ivar to be fulfilled be message's processing result
31
+ # @return [IVar] supplied ivar
32
+ def ask(message, ivar = IVar.new)
33
+ message message, ivar
34
+ end
35
+
36
+ # @note can lead to deadlocks, use only in tests or when you are sure it won't deadlock
37
+ # tells message to the actor
38
+ # @param [Object] message
39
+ # @param [Ivar] ivar to be fulfilled be message's processing result
40
+ # @return [Object] message's processing result
41
+ # @raise [Exception] ivar.reason if ivar is #rejected?
42
+ def ask!(message, ivar = IVar.new)
43
+ ask(message, ivar).value!
44
+ end
45
+
46
+ # behaves as {#tell} when no ivar and as {#ask} when ivar
47
+ def message(message, ivar = nil)
48
+ core.on_envelope Envelope.new(message, ivar, Actress.current || Thread.current, self)
49
+ return ivar || self
50
+ end
51
+
52
+ def to_s
53
+ "#<#{self.class} #{path} (#{actor_class})>"
54
+ end
55
+
56
+ alias_method :inspect, :to_s
57
+
58
+ def ==(other)
59
+ Type? other, self.class and other.send(:core) == core
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,48 @@
1
+ module Concurrent
2
+ module Actress
3
+
4
+ # taken from Algebrick
5
+ # supplies type-checking helpers whenever included
6
+ module TypeCheck
7
+
8
+ def Type?(value, *types)
9
+ types.any? { |t| value.is_a? t }
10
+ end
11
+
12
+ def Type!(value, *types)
13
+ Type?(value, *types) or
14
+ TypeCheck.error(value, 'is not', types)
15
+ value
16
+ end
17
+
18
+ def Match?(value, *types)
19
+ types.any? { |t| t === value }
20
+ end
21
+
22
+ def Match!(value, *types)
23
+ Match?(value, *types) or
24
+ TypeCheck.error(value, 'is not matching', types)
25
+ value
26
+ end
27
+
28
+ def Child?(value, *types)
29
+ Type?(value, Class) &&
30
+ types.any? { |t| value <= t }
31
+ end
32
+
33
+ def Child!(value, *types)
34
+ Child?(value, *types) or
35
+ TypeCheck.error(value, 'is not child', types)
36
+ value
37
+ end
38
+
39
+ private
40
+
41
+ def self.error(value, message, types)
42
+ raise TypeError,
43
+ "Value (#{value.class}) '#{value}' #{message} any of: #{types.join('; ')}."
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,232 @@
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
+ # The default timeout value (in seconds); used when no timeout option
42
+ # is given at initialization
43
+ TIMEOUT = 5
44
+
45
+ attr_reader :timeout, :task_executor, :operation_executor
46
+
47
+ # Initialize a new Agent with the given initial value and provided options.
48
+ #
49
+ # @param [Object] initial the initial value
50
+ # @param [Hash] opts the options used to define the behavior at update and deref
51
+ #
52
+ # @option opts [Fixnum] :timeout (TIMEOUT) maximum number of seconds before an update is cancelled
53
+ #
54
+ # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
55
+ # operation pool (for long-running operations), when `false` will execute the future on the
56
+ # global task pool (for short-running tasks)
57
+ # @option opts [object] :executor when provided will run all operations on
58
+ # this executor rather than the global thread pool (overrides :operation)
59
+ #
60
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
61
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
62
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
63
+ # returning the value returned from the proc
64
+ def initialize(initial, opts = {})
65
+ @value = initial
66
+ @rescuers = []
67
+ @validator = Proc.new { |result| true }
68
+ @timeout = opts.fetch(:timeout, TIMEOUT).freeze
69
+ self.observers = CopyOnWriteObserverSet.new
70
+ @serialized_execution = SerializedExecution.new
71
+ @task_executor = OptionsParser.get_task_executor_from(opts)
72
+ @operation_executor = OptionsParser.get_operation_executor_from(opts)
73
+ init_mutex
74
+ set_deref_options(opts)
75
+ end
76
+
77
+ # Specifies a block operation to be performed when an update operation raises
78
+ # an exception. Rescue blocks will be checked in order they were added. The first
79
+ # block for which the raised exception "is-a" subclass of the given `clazz` will
80
+ # be called. If no `clazz` is given the block will match any caught exception.
81
+ # This behavior is intended to be identical to Ruby's `begin/rescue/end` behavior.
82
+ # Any number of rescue handlers can be added. If no rescue handlers are added then
83
+ # caught exceptions will be suppressed.
84
+ #
85
+ # @param [Exception] clazz the class of exception to catch
86
+ # @yield the block to be called when a matching exception is caught
87
+ # @yieldparam [StandardError] ex the caught exception
88
+ #
89
+ # @example
90
+ # score = Concurrent::Agent.new(0).
91
+ # rescue(NoMethodError){|ex| puts "Bam!" }.
92
+ # rescue(ArgumentError){|ex| puts "Pow!" }.
93
+ # rescue{|ex| puts "Boom!" }
94
+ #
95
+ # score << proc{|current| raise ArgumentError }
96
+ # sleep(0.1)
97
+ # #=> puts "Pow!"
98
+ def rescue(clazz = StandardError, &block)
99
+ unless block.nil?
100
+ mutex.synchronize do
101
+ @rescuers << Rescuer.new(clazz, block)
102
+ end
103
+ end
104
+ self
105
+ end
106
+ alias_method :catch, :rescue
107
+ alias_method :on_error, :rescue
108
+
109
+ # A block operation to be performed after every update to validate if the new
110
+ # value is valid. If the new value is not valid then the current value is not
111
+ # updated. If no validator is provided then all updates are considered valid.
112
+ #
113
+ # @yield the block to be called after every update operation to determine if
114
+ # the result is valid
115
+ # @yieldparam [Object] value the result of the last update operation
116
+ # @yieldreturn [Boolean] true if the value is valid else false
117
+ def validate(&block)
118
+
119
+ unless block.nil?
120
+ begin
121
+ mutex.lock
122
+ @validator = block
123
+ ensure
124
+ mutex.unlock
125
+ end
126
+ end
127
+ self
128
+ end
129
+ alias_method :validates, :validate
130
+ alias_method :validate_with, :validate
131
+ alias_method :validates_with, :validate
132
+
133
+ # Update the current value with the result of the given block operation,
134
+ # block should not do blocking calls, use #post_off for blocking calls
135
+ #
136
+ # @yield the operation to be performed with the current value in order to calculate
137
+ # the new value
138
+ # @yieldparam [Object] value the current value
139
+ # @yieldreturn [Object] the new value
140
+ # @return [true, nil] nil when no block is given
141
+ def post(&block)
142
+ post_on(@task_executor, &block)
143
+ end
144
+
145
+ # Update the current value with the result of the given block operation,
146
+ # block can do blocking calls
147
+ #
148
+ # @yield the operation to be performed with the current value in order to calculate
149
+ # the new value
150
+ # @yieldparam [Object] value the current value
151
+ # @yieldreturn [Object] the new value
152
+ # @return [true, nil] nil when no block is given
153
+ def post_off(&block)
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, valid = Concurrent::timeout(@timeout) do
207
+ result = handler.call(value)
208
+ [result, validator.call(result)]
209
+ end
210
+ rescue Exception => ex
211
+ exception = ex
212
+ end
213
+
214
+ begin
215
+ mutex.lock
216
+ should_notify = if !exception && valid
217
+ @value = result
218
+ true
219
+ end
220
+ ensure
221
+ mutex.unlock
222
+ end
223
+
224
+ if should_notify
225
+ time = Time.now
226
+ observers.notify_observers { [time, self.value] }
227
+ end
228
+
229
+ try_rescue(exception)
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,319 @@
1
+ require 'thread'
2
+ require 'concurrent/configuration'
3
+ require 'concurrent/delay'
4
+ require 'concurrent/errors'
5
+ require 'concurrent/ivar'
6
+ require 'concurrent/future'
7
+ require 'concurrent/executor/thread_pool_executor'
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 synchronous, thread-safe method calls to the wrapped object.
128
+ #
129
+ # @!visibility private
130
+ class AwaitDelegator # :nodoc:
131
+
132
+ # Create a new delegator object wrapping the given `delegate` and
133
+ # protecting it with the given `mutex`.
134
+ #
135
+ # @param [Object] delegate the object to wrap and delegate method calls to
136
+ # @param [Mutex] mutex the mutex lock to use when delegating method calls
137
+ def initialize(delegate, mutex)
138
+ @delegate = delegate
139
+ @mutex = mutex
140
+ end
141
+
142
+ # Delegates method calls to the wrapped object. For performance,
143
+ # dynamically defines the given method on the delegator so that
144
+ # all future calls to `method` will not be directed here.
145
+ #
146
+ # @param [Symbol] method the method being called
147
+ # @param [Array] args zero or more arguments to the method
148
+ #
149
+ # @return [IVar] the result of the method call
150
+ #
151
+ # @raise [NameError] the object does not respond to `method` method
152
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
153
+ def method_missing(method, *args, &block)
154
+ super unless @delegate.respond_to?(method)
155
+ Async::validate_argc(@delegate, method, *args)
156
+
157
+ self.define_singleton_method(method) do |*args|
158
+ Async::validate_argc(@delegate, method, *args)
159
+ ivar = Concurrent::IVar.new
160
+ value, reason = nil, nil
161
+ begin
162
+ @mutex.synchronize do
163
+ value = @delegate.send(method, *args, &block)
164
+ end
165
+ rescue => reason
166
+ # caught
167
+ ensure
168
+ return ivar.complete(reason.nil?, value, reason)
169
+ end
170
+ end
171
+
172
+ self.send(method, *args)
173
+ end
174
+ end
175
+
176
+ # Delegates asynchronous, thread-safe method calls to the wrapped object.
177
+ #
178
+ # @!visibility private
179
+ class AsyncDelegator # :nodoc:
180
+
181
+ # Create a new delegator object wrapping the given `delegate` and
182
+ # protecting it with the given `mutex`.
183
+ #
184
+ # @param [Object] delegate the object to wrap and delegate method calls to
185
+ # @param [Mutex] mutex the mutex lock to use when delegating method calls
186
+ def initialize(delegate, executor, mutex)
187
+ @delegate = delegate
188
+ @executor = executor
189
+ @mutex = mutex
190
+ end
191
+
192
+ # Delegates method calls to the wrapped object. For performance,
193
+ # dynamically defines the given method on the delegator so that
194
+ # all future calls to `method` will not be directed here.
195
+ #
196
+ # @param [Symbol] method the method being called
197
+ # @param [Array] args zero or more arguments to the method
198
+ #
199
+ # @return [IVar] the result of the method call
200
+ #
201
+ # @raise [NameError] the object does not respond to `method` method
202
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
203
+ def method_missing(method, *args, &block)
204
+ super unless @delegate.respond_to?(method)
205
+ Async::validate_argc(@delegate, method, *args)
206
+
207
+ self.define_singleton_method(method) do |*args|
208
+ Async::validate_argc(@delegate, method, *args)
209
+ Concurrent::Future.execute(executor: @executor.value) do
210
+ @mutex.synchronize do
211
+ @delegate.send(method, *args, &block)
212
+ end
213
+ end
214
+ end
215
+
216
+ self.send(method, *args)
217
+ end
218
+ end
219
+
220
+ # Causes the chained method call to be performed asynchronously on the
221
+ # global thread pool. The method called by this method will return a
222
+ # `Future` object in the `:pending` state and the method call will have
223
+ # been scheduled on the global thread pool. The final disposition of the
224
+ # method call can be obtained by inspecting the returned `Future`.
225
+ #
226
+ # Before scheduling the method on the global thread pool a best-effort
227
+ # attempt will be made to validate that the method exists on the object
228
+ # and that the given arguments match the arity of the requested function.
229
+ # Due to the dynamic nature of Ruby and limitations of its reflection
230
+ # library, some edge cases will be missed. For more information see
231
+ # the documentation for the `validate_argc` method.
232
+ #
233
+ # @note The method call is guaranteed to be thread safe with respect to
234
+ # all other method calls against the same object that are called with
235
+ # either `async` or `await`. The mutable nature of Ruby references
236
+ # (and object orientation in general) prevent any other thread safety
237
+ # guarantees. Do NOT mix non-protected method calls with protected
238
+ # method call. Use *only* protected method calls when sharing the object
239
+ # between threads.
240
+ #
241
+ # @return [Concurrent::Future] the pending result of the asynchronous operation
242
+ #
243
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
244
+ # @raise [NameError] the object does not respond to `method` method
245
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
246
+ #
247
+ # @see Concurrent::Future
248
+ def async
249
+ raise InitializationError.new('#init_mutex was never called') unless @__async__mutex__
250
+ @__async_delegator__.value
251
+ end
252
+ alias_method :future, :async
253
+
254
+ # Causes the chained method call to be performed synchronously on the
255
+ # current thread. The method called by this method will return an
256
+ # `IVar` object in either the `:fulfilled` or `rejected` state and the
257
+ # method call will have completed. The final disposition of the
258
+ # method call can be obtained by inspecting the returned `IVar`.
259
+ #
260
+ # Before scheduling the method on the global thread pool a best-effort
261
+ # attempt will be made to validate that the method exists on the object
262
+ # and that the given arguments match the arity of the requested function.
263
+ # Due to the dynamic nature of Ruby and limitations of its reflection
264
+ # library, some edge cases will be missed. For more information see
265
+ # the documentation for the `validate_argc` method.
266
+ #
267
+ # @note The method call is guaranteed to be thread safe with respect to
268
+ # all other method calls against the same object that are called with
269
+ # either `async` or `await`. The mutable nature of Ruby references
270
+ # (and object orientation in general) prevent any other thread safety
271
+ # guarantees. Do NOT mix non-protected method calls with protected
272
+ # method call. Use *only* protected method calls when sharing the object
273
+ # between threads.
274
+ #
275
+ # @return [Concurrent::IVar] the completed result of the synchronous operation
276
+ #
277
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
278
+ # @raise [NameError] the object does not respond to `method` method
279
+ # @raise [ArgumentError] the given `args` do not match the arity of `method`
280
+ #
281
+ # @see Concurrent::IVar
282
+ def await
283
+ raise InitializationError.new('#init_mutex was never called') unless @__async__mutex__
284
+ @__await_delegator__.value
285
+ end
286
+ alias_method :delay, :await
287
+
288
+ # Set a new executor
289
+ #
290
+ # @raise [Concurrent::InitializationError] `#init_mutex` has not been called
291
+ # @raise [ArgumentError] executor has already been set
292
+ def executor=(executor)
293
+ raise InitializationError.new('#init_mutex was never called') unless @__async__mutex__
294
+ @__async__executor__.reconfigure { executor } or
295
+ raise ArgumentError.new('executor has already been set')
296
+ end
297
+
298
+ # Initialize the internal mutex and other synchronization objects. This method
299
+ # *must* be called from the constructor of the including class or explicitly
300
+ # by the caller prior to calling any other methods. If `init_mutex` is *not*
301
+ # called explicitly the async/await/executor methods will raize a
302
+ # `Concurrent::InitializationError`. This is the only way thread-safe
303
+ # initialization can be guaranteed.
304
+ #
305
+ # @note This method *must* be called from the constructor of the including
306
+ # class or explicitly by the caller prior to calling any other methods.
307
+ # This is the only way thread-safe initialization can be guaranteed.
308
+ #
309
+ # @raise [Concurrent::InitializationError] when called more than once
310
+ def init_mutex
311
+ raise InitializationError.new('#init_mutex was already called') if @__async__mutex__
312
+ (@__async__mutex__ = Mutex.new).lock
313
+ @__async__executor__ = Delay.new{ Concurrent.configuration.global_operation_pool }
314
+ @__await_delegator__ = Delay.new{ AwaitDelegator.new(self, @__async__mutex__) }
315
+ @__async_delegator__ = Delay.new{ AsyncDelegator.new(self, @__async__executor__, @__async__mutex__) }
316
+ @__async__mutex__.unlock
317
+ end
318
+ end
319
+ end