concurrent-ruby 0.7.0.rc0-x86-solaris-2.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -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