123456789_123456789_123456789_123456789_123456789_

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

Note:

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 a Proc object the Proc will be run every time the #value method is called. The Proc 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. When nil 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

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?
#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 #set_deref_options method.

Instance Method Summary

Concern::Obligation - Included

#exception,
#no_error!
#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 next_state only if current state == expected_current.

#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

#deref
#apply_deref_options,
#ns_set_deref_options

Set the options which define the operations #value performs before returning data to the caller (dereferencing).

Synchronization::LockableObject - Inherited

Constructor Details

.new(opts = {}) { ... } ⇒ Delay

Create a new Delay in the :pending state.

Parameters:

  • opts (Hash) (defaults to: {})

    the options used to define the behavior at update and deref and to specify the executor on which to perform actions

Options Hash (opts):

  • :executor (Executor)

    when set use the given Executor instance. Three special values are also supported: :io returns the global pool for long, blocking (IO) tasks, :fast returns the global pool for short, fast operations, and :immediate returns the global ImmediateExecutor object.

  • :dup_on_deref (Boolean) — default: false

    Call #dup before returning the data from #value

  • :freeze_on_deref (Boolean) — default: false

    Call #freeze before returning the data from #value

  • :copy_on_deref (Proc) — default: nil

    When calling the #value method, call the given proc passing the internal value as the sole argument then return the new value returned from the proc.

Yields:

  • the delayed operation to perform

Raises:

  • (ArgumentError)

    if no block is given

[ GitHub ]

  
# 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)

This method is for internal use only.
[ GitHub ]

  
# 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_from_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?

Yields:

  • the delayed operation to perform

Returns:

  • (true, false)

    if success

[ GitHub ]

  
# 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

Note:

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.

Parameters:

  • timeout (Numeric) (defaults to: nil)

    the maximum number of seconds to wait

Returns:

  • (Object)

    the current value of the object

[ GitHub ]

  
# 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
      apply_deref_options(@value)
    end
  end
end

#value!(timeout = nil) ⇒ Object

Note:

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).

Parameters:

  • timeout (Numeric) (defaults to: nil)

    the maximum number of seconds to wait

Returns:

  • (Object)

    the current value of the object

Raises:

  • (Exception)

    when #rejected? raises #reason

[ GitHub ]

  
# 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

Note:

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.

Parameters:

  • timeout (Integer) (defaults to: nil)

    (nil) the maximum number of seconds to wait for the value to be computed. When nil the caller will block indefinitely.

Returns:

[ GitHub ]

  
# 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