Class: Concurrent::Delay
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
|
|
Instance Chain:
|
|
Inherits: |
Concurrent::Synchronization::LockableObject
|
Defined in: | lib/concurrent-ruby/concurrent/delay.rb |
Overview
The default behavior of Delay
is to block indefinitely when calling either #value or #wait, executing the delayed operation on the current thread. This makes the timeout
value completely irrelevant. To enable non-blocking behavior, use the executor constructor option. This will cause the delayed operation to be execute on the given executor, allowing the call to timeout.
Lazy evaluation of a block yielding an immutable result. Useful for expensive operations that may never be needed. It may be non-blocking, supports the Concern::Obligation
interface, and accepts the injection of custom executor upon which to execute the block. Processing of block will be deferred until the first time #value is called. At that time the caller can choose to return immediately and let the block execute asynchronously, block indefinitely, or block with a timeout.
When a Delay
is created its state is set to pending
. The value and reason are both nil
. The first time the #value method is called the enclosed opration will be run and the calling thread will block. Other threads attempting to call #value will block as well. Once the operation is complete the value will be set to the result of the operation or the reason will be set to the raised exception, as appropriate. All threads blocked on #value will return. Subsequent calls to #value will immediately return the cached value. The operation will only be run once. This means that any side effects created by the operation will only happen once as well.
Delay
includes the Concern::Dereferenceable
mixin to support thread safety of the reference returned by #value.
## Copy Options
::Object
references in Ruby are mutable. This can lead to serious problems when the #value of an object is a mutable reference. Which is always the case unless the value is a Fixnum
, Symbol
, or similar “primitive” data type. Each instance can be configured with a few options that can help protect the program from potentially dangerous operations. Each of these options can be optionally set when the object instance is created:
-
:dup_on_deref
When true the object will call the#dup
method on the #value object every time the #value method is called (default: false) -
:freeze_on_deref
When true the object will call the#freeze
method on the #value object every time the #value method is called (default: false) -
:copy_on_deref
When given aProc
object theProc
will be run every time the #value method is called. TheProc
will be given the current #value as its only argument and the result returned by the block will be the return value of the #value call. Whennil
this option will be ignored (default: nil)
When multiple deref options are set the order of operations is strictly defined. The order of deref operations is:
-
:copy_on_deref
-
:dup_on_deref
-
:freeze_on_deref
Because of this ordering there is no need to #freeze
an object created by a provided :copy_on_deref
block. Simply set :freeze_on_deref
to true
. Setting both :dup_on_deref
to true
and :freeze_on_deref
to true
is as close to the behavior of a “pure” functional language (like Erlang, Clojure, or Haskell) as we are likely to get in Ruby.
Class Method Summary
-
.new(opts = {}) { ... } ⇒ Delay
constructor
Create a new
Delay
in the:pending
state.
Instance Attribute Summary
Concern::Obligation
- Included
#complete? | Has the obligation completed processing? |
#fulfilled? | Has the obligation been fulfilled? |
#incomplete? | Is the obligation still awaiting completion of processing? |
#pending? | Is obligation completion still pending? |
#realized? | Alias for Concern::Obligation#fulfilled?. |
#rejected? | Has the obligation been rejected? |
#state | The current state of the obligation. |
#unscheduled? | Is the obligation still unscheduled? |
#state= |
Concern::Dereferenceable
- Included
#value | Return the value this object represents after applying the options specified by the |
Instance Method Summary
-
#reconfigure { ... } ⇒ true, false
Reconfigures the block returning the value if still
#incomplete?
-
#value(timeout = nil) ⇒ Object
Return the value this object represents after applying the options specified by the
#set_deref_options
method. -
#value!(timeout = nil) ⇒ Object
Return the value this object represents after applying the options specified by the
#set_deref_options
method. -
#wait(timeout = nil) ⇒ Object
Return the value this object represents after applying the options specified by the
#set_deref_options
method. - #execute_task_once private Internal use only
Concern::Obligation
- Included
#exception, | |
#no_error! | Alias for Concern::Obligation#wait!. |
#reason | If an exception was raised during processing this will return the exception object. |
#value | The current value of the obligation. |
#value! | The current value of the obligation. |
#wait | Wait until obligation is complete or the timeout has been reached. |
#wait! | Wait until obligation is complete or the timeout is reached. |
#compare_and_set_state | Atomic compare and set operation State is set to |
#event, #get_arguments_from, | |
#if_state | Executes the block within mutex if current state is included in expected_states. |
#init_obligation, | |
#ns_check_state? | Am I in the current state? |
#ns_set_state, #set_state |
Concern::Dereferenceable
- Included
Synchronization::LockableObject
- Inherited
Constructor Details
.new(opts = {}) { ... } ⇒ Delay
Create a new Delay
in the :pending
state.
# File 'lib/concurrent-ruby/concurrent/delay.rb', line 62
def initialize(opts = {}, &block) raise ArgumentError.new('no block given') unless block_given? super(&nil) synchronize { ns_initialize(opts, &block) } end
Instance Method Details
#execute_task_once (private)
# File 'lib/concurrent-ruby/concurrent/delay.rb', line 173
def execute_task_once # :nodoc: # this function has been optimized for performance and # should not be modified without running new benchmarks execute = task = nil synchronize do execute = @evaluation_started = true unless @evaluation_started task = @task end if execute executor = Options. (executor: @executor) executor.post do begin result = task.call success = true rescue => ex reason = ex end synchronize do set_state(success, result, reason) event.set end end end end
#reconfigure { ... } ⇒ true
, false
Reconfigures the block returning the value if still #incomplete?
# File 'lib/concurrent-ruby/concurrent/delay.rb', line 146
def reconfigure(&block) synchronize do raise ArgumentError.new('no block given') unless block_given? unless @evaluation_started @task = block true else false end end end
#value(timeout = nil) ⇒ Object
The default behavior of Delay
is to block indefinitely when calling either value
or #wait, executing the delayed operation on the current thread. This makes the timeout
value completely irrelevant. To enable non-blocking behavior, use the Concurrent.executor constructor option. This will cause the delayed operation to be execute on the given executor, allowing the call to timeout.
Return the value this object represents after applying the options specified by the #set_deref_options
method. If the delayed operation raised an exception this method will return nil. The exception object can be accessed via the #reason
method.
# File 'lib/concurrent-ruby/concurrent/delay.rb', line 77
def value(timeout = nil) if @executor # TODO (pitr 12-Sep-2015): broken unsafe read? super else # this function has been optimized for performance and # should not be modified without running new benchmarks synchronize do execute = @evaluation_started = true unless @evaluation_started if execute begin set_state(true, @task.call, nil) rescue => ex set_state(false, nil, ex) end elsif incomplete? raise IllegalOperationError, 'Recursive call to #value during evaluation of the Delay' end end if @do_nothing_on_deref @value else (@value) end end end
#value!(timeout = nil) ⇒ Object
The default behavior of Delay
is to block indefinitely when calling either #value or #wait, executing the delayed operation on the current thread. This makes the timeout
value completely irrelevant. To enable non-blocking behavior, use the Concurrent.executor constructor option. This will cause the delayed operation to be execute on the given executor, allowing the call to timeout.
Return the value this object represents after applying the options specified by the #set_deref_options
method. If the delayed operation raised an exception, this method will raise that exception (even when) the operation has already been executed).
# File 'lib/concurrent-ruby/concurrent/delay.rb', line 113
def value!(timeout = nil) if @executor super else result = value raise @reason if @reason result end end
#wait(timeout = nil) ⇒ Object
The default behavior of Delay
is to block indefinitely when calling either #value or wait
, executing the delayed operation on the current thread. This makes the timeout
value completely irrelevant. To enable non-blocking behavior, use the Concurrent.executor constructor option. This will cause the delayed operation to be execute on the given executor, allowing the call to timeout.
Return the value this object represents after applying the options specified by the #set_deref_options
method.
# File 'lib/concurrent-ruby/concurrent/delay.rb', line 132
def wait(timeout = nil) if @executor execute_task_once super(timeout) else value end self end