A mixin module that provides simple asynchronous behavior to any standard class/object or object.
"`cucumber Feature:
As a stateful, plain old Ruby class/object I want safe, asynchronous behavior So my long-running methods don't block the main thread
"`
Stateful, mutable objects must be managed carefully when used asynchronously. But Ruby is an object-oriented language so designing with objects and classes plays to Ruby's strengths and is often more natural to many Ruby programmers. The `Async` module is a way to mix simple yet powerful asynchronous capabilities into any plain old Ruby object or class. These capabilities provide a reasonable level of thread safe guarantees when used correctly.
When this module is mixed into a class or object it provides to new methods: `async` and `await`. These methods are thread safe with respect to the enclosing object. The former method allows methods to be called asynchronously by posting to the global thread pool. The latter allows a method to be called synchronously on the current thread but does so safely with respect to any pending asynchronous method calls. Both methods return an `IVar` which can be inspected for the result of the method call. Calling a method with `async` will return a `:pending` `IVar` whereas `await` will return a `:complete` `IVar`.
Very loosely based on the `async` and `await` keywords in C#.
### An Important Note About Thread Safe Guarantees
> Thread safe guarantees can only be made when asynchronous method calls > are not mixed with synchronous method calls. Use only synchronous calls > when the object is used exclusively on a single thread. Use only > `async` and `await` when the object is shared between threads. Once you > call a method using `async`, you should no longer call any methods > directly on the object. Use `async` and `await` exclusively from then on. > With careful programming it is possible to switch back and forth but it's > also very easy to create race conditions and break your application. > Basically, it's "async all the way down."
@example
class Echo include Concurrent::Async def echo(msg) sleep(rand) print "#{msg}\n" nil end end horn = Echo.new horn.echo('zero') # synchronous, not thread-safe horn.async.echo('one') # asynchronous, non-blocking, thread-safe horn.await.echo('two') # synchronous, blocking, thread-safe
@see Concurrent::IVar
@!visibility private
# File lib/concurrent/async.rb, line 116 def self.included(base) base.singleton_class.send(:alias_method, :original_new, :new) base.extend(ClassMethods) super(base) end
Check for the presence of a method on an object and determine if a given set of arguments matches the required arity.
@param [Object] obj the object to check against @param [Symbol] method the method to check the object for @param [Array] args zero or more arguments for the arity check
@raise [NameError] the object does not respond to `method` method @raise [ArgumentError] the given `args` do not match the arity of `method`
@note This check is imperfect because of the way Ruby reports the arity of
methods with a variable number of arguments. It is possible to determine if too few arguments are given but impossible to determine if too many arguments are given. This check may also fail to recognize dynamic behavior of the object, such as methods simulated with `method_missing`.
@see www.ruby-doc.org/core-2.1.1/Method.html#method-i-arity Method#arity @see ruby-doc.org/core-2.1.0/Object.html#method-i-respond_to-3F Object#respond_to? @see www.ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing BasicObject#method_missing
@!visibility private
# File lib/concurrent/async.rb, line 104 def self.validate_argc(obj, method, *args) argc = args.length arity = obj.method(method).arity if arity >= 0 && argc != arity raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity})") elsif arity < 0 && (arity = (arity + 1).abs) > argc raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity}..*)") end end
Causes the chained method call to be performed asynchronously on the global thread pool. The method called by this method will return a future object in the `:pending` state and the method call will have been scheduled on the global thread pool. The final disposition of the method call can be obtained by inspecting the returned future.
Before scheduling the method on the global thread pool a best-effort attempt will be made to validate that the method exists on the object and that the given arguments match the arity of the requested function. Due to the dynamic nature of Ruby and limitations of its reflection library, some edge cases will be missed. For more information see the documentation for the `validate_argc` method.
@!macro [attach] async_thread_safety_warning
@note The method call is guaranteed to be thread safe with respect to all other method calls against the same object that are called with either `async` or `await`. The mutable nature of Ruby references (and object orientation in general) prevent any other thread safety guarantees. Do NOT mix non-protected method calls with protected method call. Use *only* protected method calls when sharing the object between threads.
@return [Concurrent::IVar] the pending result of the asynchronous operation
@raise [NameError] the object does not respond to `method` method @raise [ArgumentError] the given `args` do not match the arity of `method`
@see Concurrent::IVar
# File lib/concurrent/async.rb, line 214 def async @__async_delegator__.value end
Causes the chained method call to be performed synchronously on the current thread. The method called by this method will return an `IVar` object in either the `:fulfilled` or `rejected` state and the method call will have completed. The final disposition of the method call can be obtained by inspecting the returned `IVar`.
Before scheduling the method on the global thread pool a best-effort attempt will be made to validate that the method exists on the object and that the given arguments match the arity of the requested function. Due to the dynamic nature of Ruby and limitations of its reflection library, some edge cases will be missed. For more information see the documentation for the `validate_argc` method.
@!macro async_thread_safety_warning
@return [Concurrent::IVar] the completed result of the synchronous operation
@raise [NameError] the object does not respond to `method` method @raise [ArgumentError] the given `args` do not match the arity of `method`
@see Concurrent::IVar
# File lib/concurrent/async.rb, line 239 def await @__await_delegator__.value end
Set a new executor.
@raise [ArgumentError] executor has already been set.
# File lib/concurrent/async.rb, line 246 def executor=(executor) @__async_executor__.reconfigure { executor } or raise ArgumentError.new('executor has already been set') end
Initialize the internal serializer and other stnchronization mechanisms.
@note This method must be called immediately upon object construction.
This is the only way thread-safe initialization can be guaranteed.
@raise [Concurrent::InitializationError] when called more than once
@!visibility private @deprecated
# File lib/concurrent/async.rb, line 260 def init_mutex deprecated 'mutex synchronization now happens automatically' init_synchronization rescue InitializationError # suppress end
Generated with the Darkfish Rdoc Generator 2.