Class: Concurrent::Delay
- Inherits:
-
Synchronization::LockableObject
- Object
- Synchronization::LockableObject
- Concurrent::Delay
- Includes:
- Concern::Obligation
- Defined in:
- lib/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 Concurrent::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 thevalue
object every time the#value
method is called (default: false):freeze_on_deref
When true the object will call the#freeze
method on thevalue
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 currentvalue
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.
Instance Method Summary collapse
-
#initialize(opts = {}) { ... } ⇒ Delay
constructor
Create a new
Delay
in the:pending
state. -
#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. -
#complete? ⇒ Boolean
included
from Concern::Obligation
Has the obligation completed processing?.
- #exception(*args) ⇒ undocumented included from Concern::Obligation
-
#fulfilled? ⇒ Boolean
(also: #realized?)
included
from Concern::Obligation
Has the obligation been fulfilled?.
-
#incomplete? ⇒ Boolean
included
from Concern::Obligation
Is the obligation still awaiting completion of processing?.
-
#pending? ⇒ Boolean
included
from Concern::Obligation
Is obligation completion still pending?.
-
#reason ⇒ Exception
included
from Concern::Obligation
If an exception was raised during processing this will return the exception object.
-
#rejected? ⇒ Boolean
included
from Concern::Obligation
Has the obligation been rejected?.
-
#state ⇒ Symbol
included
from Concern::Obligation
The current state of the obligation.
-
#unscheduled? ⇒ Boolean
included
from Concern::Obligation
Is the obligation still unscheduled?.
-
#wait!(timeout = nil) ⇒ Obligation
(also: #no_error!)
included
from Concern::Obligation
Wait until obligation is complete or the timeout is reached.
Constructor Details
#initialize(opts = {}) { ... } ⇒ Delay
Create a new Delay
in the :pending
state.
62 63 64 65 66 |
# File 'lib/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
#reconfigure { ... } ⇒ true, false
Reconfigures the block returning the value if still #incomplete?
146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/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 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 execption object
can be accessed via the #reason
method.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/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 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).
113 114 115 116 117 118 119 120 121 |
# File 'lib/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 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.
132 133 134 135 136 137 138 139 140 |
# File 'lib/concurrent/delay.rb', line 132 def wait(timeout = nil) if @executor execute_task_once super(timeout) else value end self end |
#complete? ⇒ Boolean Originally defined in module Concern::Obligation
Has the obligation completed processing?
#exception(*args) ⇒ undocumented Originally defined in module Concern::Obligation
#fulfilled? ⇒ Boolean Also known as: realized? Originally defined in module Concern::Obligation
Has the obligation been fulfilled?
#incomplete? ⇒ Boolean Originally defined in module Concern::Obligation
Is the obligation still awaiting completion of processing?
#pending? ⇒ Boolean Originally defined in module Concern::Obligation
Is obligation completion still pending?
#reason ⇒ Exception Originally defined in module Concern::Obligation
If an exception was raised during processing this will return the
exception object. Will return nil
when the state is pending or if
the obligation has been successfully fulfilled.
#rejected? ⇒ Boolean Originally defined in module Concern::Obligation
Has the obligation been rejected?
#state ⇒ Symbol Originally defined in module Concern::Obligation
The current state of the obligation.
#unscheduled? ⇒ Boolean Originally defined in module Concern::Obligation
Is the obligation still unscheduled?
#wait!(timeout = nil) ⇒ Obligation Also known as: no_error! Originally defined in module Concern::Obligation
Wait until obligation is complete or the timeout is reached. Will re-raise any exceptions raised during processing (but will not raise an exception on timeout).