Class List Method List File List

# Class: Concurrent::Future

Inherits:
  IVar
  - Object
  - Synchronization::LockableObject
  - IVar
  - Concurrent::Future
  show all
Defined in:
  lib/concurrent/future.rb

## Overview

`Future` is inspired by Clojure's future function. A future represents a promise to complete an action at some time in the future. The action is atomic and permanent. The idea behind a future is to send an operation for asynchronous completion, do other stuff, then return and retrieve the result of the async operation at a later time. `Future`s run on the global thread pool.

    Feature:
      As a highly responsive Ruby application
      I want long-running tasks on a separate thread
      So I can perform other tasks without waiting

`Future`s have several possible states: :unscheduled, :pending, :processing, :rejected, or :fulfilled. These are also aggregated as `#incomplete?` and `#complete?`. When a `Future` is created it is set to :unscheduled. Once the `#execute` method is called the state becomes :pending. Once a job is pulled from the thread pool's queue and is given to a thread for processing (often immediately upon `#post`) the state becomes :processing. The future will remain in this state until processing is complete. A future that is in the :unscheduled, :pending, or :processing is considered `#incomplete?`. A `#complete?` `Future` is either :rejected, indicating that an exception was thrown during processing, or :fulfilled, indicating success. If a `Future` is :fulfilled its `#value` will be updated to reflect the result of the operation. If :rejected the `reason` will be updated with a reference to the thrown exception. The predicate methods `#unscheduled?`, `#pending?`, `#rejected?`, and `#fulfilled?` can be called at any time to obtain the state of the `Future`, as can the `#state` method, which returns a symbol.

Retrieving the value of a `Future` is done through the `#value` (alias: `#deref`) method. Obtaining the value of a `Future` is a potentially blocking operation. When a `Future` is :rejected a call to `#value` will return `nil` immediately. When a `Future` is :fulfilled a call to `#value` will immediately return the current value. When a `Future` is :pending a call to `#value` will block until the `Future` is either :rejected or :fulfilled. A timeout value can be passed to `#value` to limit how long the call will block. If `nil` the call will block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the maximum number of seconds to block.

The constructor can also be given zero or more processing options. Currently the only supported options are those recognized by the Dereferenceable module.

The `Future` class also includes the behavior of the Ruby standard library Observable module, but does so in a thread-safe way. On fulfillment or rejection all observers will be notified according to the normal `Observable` behavior. The observer callback function will be called with three parameters: the `Time` of fulfillment/rejection, the final `value`, and the final `reason`. Observers added after fulfillment/rejection will still be notified as normal. The notification will occur on the same thread that processed the job.

### Examples

A fulfilled example:

    require 'concurrent'
    require 'thread'   # for Queue
    require 'open-uri' # for open(uri)

    class Ticker
      def get_year_end_closing(symbol, year)
        uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year}&g=m"
        data = open(uri) {|f| f.collect{|line| line.strip } }
        data[1].split(',')[4].to_f
      end
    end

    # Future
    price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013) }
    price.state #=> :pending
    price.pending? #=> true
    price.value(0) #=> nil (does not block)

    sleep(1)    # do other stuff

    price.value #=> 63.65 (after blocking if necessary)
    price.state #=> :fulfilled
    price.fulfilled? #=> true
    price.value #=> 63.65

A rejected example:

    count = Concurrent::Future.execute{ sleep(10); raise StandardError.new("Boom!") }
    count.state #=> :pending
    count.pending? #=> true

    count.value #=> nil (after blocking)
    count.rejected? #=> true
    count.reason #=> #<StandardError: Boom!>

An example with observation:

    class Ticker
      Stock = Struct.new(:symbol, :name, :exchange)

      def update(time, value, reason)
        ticker = value.collect do |symbol|
          Stock.new(symbol['symbol'], symbol['name'], symbol['exch'])
        end

        output = ticker.join("\n")
        print "#{output}\n"
      end
    end

    yahoo = Finance.new('YAHOO')
    future = Concurrent::Future.new { yahoo.update.suggested_symbols }
    future.add_observer(Ticker.new)
    future.execute

    # do important stuff...

    #>> #<struct Ticker::Stock symbol="YHOO", name="Yahoo! Inc.", exchange="NMS">
    #>> #<struct Ticker::Stock symbol="YHO.DE", name="Yahoo! Inc.", exchange="GER">
    #>> #<struct Ticker::Stock symbol="YAHOY", name="Yahoo Japan Corporation", exchange="PNK">
    #>> #<struct Ticker::Stock symbol="YAHOF", name="YAHOO JAPAN CORP", exchange="PNK">
    #>> #<struct Ticker::Stock symbol="YOJ.SG", name="YAHOO JAPAN", exchange="STU">
    #>> #<struct Ticker::Stock symbol="YHO.SG", name="YAHOO", exchange="STU">
    #>> #<struct Ticker::Stock symbol="YHOO.BA", name="Yahoo! Inc.", exchange="BUE">
    #>> #<struct Ticker::Stock symbol="YHO.DU", name="YAHOO", exchange="DUS">
    #>> #<struct Ticker::Stock symbol="YHO.HM", name="YAHOO", exchange="HAM">
    #>> #<struct Ticker::Stock symbol="YHO.BE", name="YAHOO", exchange="BER">

## Copy Options

Object references in Ruby are mutable. This can lead to serious problems when the Concern::Obligation#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:

- Ruby Observable module
- Clojure's future function
- java.util.concurrent.Future

## Class Method Summary (collapse)

- + (Future) execute(opts = {}) { ... }

  Create a new `Future` object with the given block, execute it, and return the `:pending` object.

## Instance Method Summary (collapse)

- - (Boolean) cancel

  Attempt to cancel the operation if it has not already processed.

- - (Boolean) cancelled?

  Has the operation been successfully cancelled?.

- - (Future) execute

  Execute an `:unscheduled` `Future`.

- - (Future) initialize(opts = {}) { ... } constructor

  Create a new `Future` in the `:unscheduled` state.

- - (IVar) set(value = NULL) { ... }

  Set the `IVar` to a value and wake or notify all threads waiting on it.

- - (Boolean) wait_or_cancel(timeout)

  Wait the given number of seconds for the operation to complete.

## Constructor Details

### - (`Future`) initialize(opts = {}) { ... }

Create a new `Future` in the `:unscheduled` 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: `:task` returns the global task pool, `:operation` returns the global operation pool, and `:immediate` returns a new `ImmediateExecutor` object.

- :dup_on_deref (`Boolean`) — default: `false` —

  Call `#dup` before returning the data from Concern::Obligation#value

- :freeze_on_deref (`Boolean`) — default: `false` —

  Call `#freeze` before returning the data from Concern::Obligation#value

- :copy_on_deref (`Proc`) — default: `nil` —

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

- :args (`object`, `Array`) —

  zero or more arguments to be passed the task block on execution

Yields:

- the asynchronous operation to perform

Raises:

- (`ArgumentError`) —

  if no block is given

    30
    31
    32
    33

    # File 'lib/concurrent/future.rb', line 30

    def initialize(opts = {}, &block)
      raise ArgumentError.new('no block given') unless block_given?
      super(NULL, opts.merge(__task_from_block__: block), &nil)
    end

## Class Method Details

### + (`Future`) execute(opts = {}) { ... }

Create a new `Future` object with the given block, execute it, and return the `:pending` object.

Examples:

    future = Concurrent::Future.execute{ sleep(1); 42 }
    future.state #=> :pending

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: `:task` returns the global task pool, `:operation` returns the global operation pool, and `:immediate` returns a new `ImmediateExecutor` object.

- :dup_on_deref (`Boolean`) — default: `false` —

  Call `#dup` before returning the data from Concern::Obligation#value

- :freeze_on_deref (`Boolean`) — default: `false` —

  Call `#freeze` before returning the data from Concern::Obligation#value

- :copy_on_deref (`Proc`) — default: `nil` —

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

- :args (`object`, `Array`) —

  zero or more arguments to be passed the task block on execution

Yields:

- the asynchronous operation to perform

Returns:

- (`Future`) —

  the newly created `Future` in the `:pending` state

Raises:

- (`ArgumentError`) —

  if no block is given

    74
    75
    76

    # File 'lib/concurrent/future.rb', line 74

    def self.execute(opts = {}, &block)
      Future.new(opts, &block).execute
    end

## Instance Method Details

### - (`Boolean`) cancel

Attempt to cancel the operation if it has not already processed. The operation can only be cancelled while still `pending`. It cannot be cancelled once it has begun processing or has completed.

Returns:

- (`Boolean`) —

  was the operation successfully cancelled.

    96
    97
    98
    99
    100
    101
    102
    103

    # File 'lib/concurrent/future.rb', line 96

    def cancel
      if compare_and_set_state(:cancelled, :pending)
        complete(false, nil, CancelledOperationError.new)
        true
      else
        false
      end
    end

### - (`Boolean`) cancelled?

Has the operation been successfully cancelled?

Returns:

- (`Boolean`)

    108
    109
    110

    # File 'lib/concurrent/future.rb', line 108

    def cancelled?
      state == :cancelled
    end

### - (`Future`) execute

Execute an `:unscheduled` `Future`. Immediately sets the state to `:pending` and passes the block to a new thread/thread pool for eventual execution. Does nothing if the `Future` is in any state other than `:unscheduled`.

Examples:

Instance and execute in separate steps

    future = Concurrent::Future.new{ sleep(1); 42 }
    future.state #=> :unscheduled
    future.execute
    future.state #=> :pending

Instance and execute in one line

    future = Concurrent::Future.new{ sleep(1); 42 }.execute
    future.state #=> :pending

Returns:

- (`Future`) —

  a reference to `self`

    50
    51
    52
    53
    54
    55

    # File 'lib/concurrent/future.rb', line 50

    def execute
      if compare_and_set_state(:pending, :unscheduled)
        @executor.post{ safe_execute(@task, @args) }
        self
      end
    end

### - (`IVar`) set(value = NULL) { ... }

Set the `IVar` to a value and wake or notify all threads waiting on it.

Parameters:

- value (`Object`) (defaults to: `NULL`) —

  the value to store in the `IVar`

Yields:

- A block operation to use for setting the value

Returns:

- (`IVar`) —

  self

Raises:

- (`ArgumentError`) —

  if both a value and a block are given

- (`Concurrent::MultipleAssignmentError`) —

  if the `IVar` has already been set or otherwise completed

    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89

    # File 'lib/concurrent/future.rb', line 79

    def set(value = NULL, &block)
      check_for_block_or_value!(block_given?, value)
      synchronize do
        if @state != :unscheduled
          raise MultipleAssignmentError
        else
          @task = block || Proc.new { value }
        end
      end
      execute
    end

### - (`Boolean`) wait_or_cancel(timeout)

Wait the given number of seconds for the operation to complete. On timeout attempt to cancel the operation.

Parameters:

- timeout (`Numeric`) —

  the maximum time in seconds to wait.

Returns:

- (`Boolean`) —

  true if the operation completed before the timeout else false

    118
    119
    120
    121
    122
    123
    124
    125
    126

    # File 'lib/concurrent/future.rb', line 118

    def wait_or_cancel(timeout)
      wait(timeout)
      if complete?
        true
      else
        cancel
        false
      end
    end