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.
- checksums.yaml +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/ext/concurrent_ruby_ext/atomic_reference.c +78 -0
- data/ext/concurrent_ruby_ext/atomic_reference.h +12 -0
- data/ext/concurrent_ruby_ext/extconf.rb +59 -0
- data/ext/concurrent_ruby_ext/rb_concurrent.c +28 -0
- data/lib/concurrent.rb +45 -0
- data/lib/concurrent/actress.rb +221 -0
- data/lib/concurrent/actress/ad_hoc.rb +20 -0
- data/lib/concurrent/actress/context.rb +98 -0
- data/lib/concurrent/actress/core.rb +228 -0
- data/lib/concurrent/actress/core_delegations.rb +42 -0
- data/lib/concurrent/actress/envelope.rb +41 -0
- data/lib/concurrent/actress/errors.rb +14 -0
- data/lib/concurrent/actress/reference.rb +64 -0
- data/lib/concurrent/actress/type_check.rb +48 -0
- data/lib/concurrent/agent.rb +232 -0
- data/lib/concurrent/async.rb +319 -0
- data/lib/concurrent/atomic.rb +46 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
- data/lib/concurrent/atomic/atomic_fixnum.rb +162 -0
- data/lib/concurrent/atomic/condition.rb +67 -0
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +118 -0
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +117 -0
- data/lib/concurrent/atomic/count_down_latch.rb +116 -0
- data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
- data/lib/concurrent/atomic/event.rb +98 -0
- data/lib/concurrent/atomic/thread_local_var.rb +117 -0
- data/lib/concurrent/atomic_reference/concurrent_update_error.rb +7 -0
- data/lib/concurrent/atomic_reference/delegated_update.rb +28 -0
- data/lib/concurrent/atomic_reference/direct_update.rb +28 -0
- data/lib/concurrent/atomic_reference/jruby.rb +8 -0
- data/lib/concurrent/atomic_reference/mutex_atomic.rb +47 -0
- data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +24 -0
- data/lib/concurrent/atomic_reference/rbx.rb +16 -0
- data/lib/concurrent/atomic_reference/ruby.rb +16 -0
- data/lib/concurrent/atomics.rb +10 -0
- data/lib/concurrent/channel/buffered_channel.rb +85 -0
- data/lib/concurrent/channel/channel.rb +41 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +34 -0
- data/lib/concurrent/channel/waitable_list.rb +40 -0
- data/lib/concurrent/channels.rb +5 -0
- data/lib/concurrent/collection/blocking_ring_buffer.rb +71 -0
- data/lib/concurrent/collection/priority_queue.rb +305 -0
- data/lib/concurrent/collection/ring_buffer.rb +59 -0
- data/lib/concurrent/collections.rb +3 -0
- data/lib/concurrent/configuration.rb +158 -0
- data/lib/concurrent/dataflow.rb +91 -0
- data/lib/concurrent/delay.rb +112 -0
- data/lib/concurrent/dereferenceable.rb +101 -0
- data/lib/concurrent/errors.rb +30 -0
- data/lib/concurrent/exchanger.rb +34 -0
- data/lib/concurrent/executor/cached_thread_pool.rb +44 -0
- data/lib/concurrent/executor/executor.rb +229 -0
- data/lib/concurrent/executor/fixed_thread_pool.rb +33 -0
- data/lib/concurrent/executor/immediate_executor.rb +16 -0
- data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
- data/lib/concurrent/executor/java_fixed_thread_pool.rb +33 -0
- data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +187 -0
- data/lib/concurrent/executor/per_thread_executor.rb +24 -0
- data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
- data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +32 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +73 -0
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +286 -0
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +72 -0
- data/lib/concurrent/executor/safe_task_executor.rb +35 -0
- data/lib/concurrent/executor/serialized_execution.rb +90 -0
- data/lib/concurrent/executor/single_thread_executor.rb +35 -0
- data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
- data/lib/concurrent/executor/timer_set.rb +143 -0
- data/lib/concurrent/executors.rb +9 -0
- data/lib/concurrent/future.rb +124 -0
- data/lib/concurrent/ivar.rb +111 -0
- data/lib/concurrent/logging.rb +17 -0
- data/lib/concurrent/mvar.rb +200 -0
- data/lib/concurrent/obligation.rb +171 -0
- data/lib/concurrent/observable.rb +40 -0
- data/lib/concurrent/options_parser.rb +46 -0
- data/lib/concurrent/promise.rb +169 -0
- data/lib/concurrent/scheduled_task.rb +78 -0
- data/lib/concurrent/supervisor.rb +343 -0
- data/lib/concurrent/timer_task.rb +341 -0
- data/lib/concurrent/tvar.rb +252 -0
- data/lib/concurrent/utilities.rb +3 -0
- data/lib/concurrent/utility/processor_count.rb +150 -0
- data/lib/concurrent/utility/timeout.rb +35 -0
- data/lib/concurrent/utility/timer.rb +21 -0
- data/lib/concurrent/version.rb +3 -0
- data/lib/concurrent_ruby.rb +1 -0
- data/lib/concurrent_ruby_ext.so +0 -0
- data/lib/extension_helper.rb +9 -0
- 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
|