Class: Concurrent::Delay — Concurrent Ruby

# Class: Concurrent::Delay

Inherits:
  Synchronization::LockableObject
  - Object
  - Synchronization::LockableObject
  - Concurrent::Delay
  show all

Includes:
  Concern::Obligation

Defined in:
  lib/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 `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 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.

See Also:

- Concern::Dereferenceable

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

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

    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?`

Yields:

- the delayed operation to perform

Returns:

- (`true`, `false`) —

  if success

    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`

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.

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.

Parameters:

- timeout (`Numeric`) (defaults to: `nil`) —

  the maximum number of seconds to wait

Returns:

- (`Object`) —

  the current value of the object

    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
          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 `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`

    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`

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.

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:

- (`Object`) —

  self

    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?

Returns:

- (`Boolean`)

### #exception(*args) ⇒ `undocumented` Originally defined in module Concern::Obligation

Examples:

allows Obligation to be risen

    rejected_ivar = Ivar.new.fail
    raise rejected_ivar

### #fulfilled? ⇒ `Boolean` Also known as: realized? Originally defined in module Concern::Obligation

Has the obligation been fulfilled?

Returns:

- (`Boolean`)

### #incomplete? ⇒ `Boolean` Originally defined in module Concern::Obligation

Is the obligation still awaiting completion of processing?

Returns:

- (`Boolean`)

### #pending? ⇒ `Boolean` Originally defined in module Concern::Obligation

Is obligation completion still pending?

Returns:

- (`Boolean`)

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

Returns:

- (`Exception`) —

  the exception raised during processing or `nil`

### #rejected? ⇒ `Boolean` Originally defined in module Concern::Obligation

Has the obligation been rejected?

Returns:

- (`Boolean`)

### #state ⇒ `Symbol` Originally defined in module Concern::Obligation

The current state of the obligation.

Returns:

- (`Symbol`) —

  the current state

### #unscheduled? ⇒ `Boolean` Originally defined in module Concern::Obligation

Is the obligation still unscheduled?

Returns:

- (`Boolean`)

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

Parameters:

- timeout (`Numeric`) (defaults to: `nil`) —

  the maximum time in seconds to wait.

Returns:

- (`Obligation`) —

  self

Raises:

- (`Exception`) —

  raises the reason when rejected