class Concurrent::IVar

An ‘IVar` is like a future that you can assign. As a future is a value that is being computed that you can wait on, an `IVar` is a value that is waiting to be assigned, that you can wait on. `IVars` are single assignment and deterministic.

Then, express futures as an asynchronous computation that assigns an ‘IVar`. The `IVar` becomes the primitive on which [futures](Future) and [dataflow](Dataflow) are built.

An ‘IVar` is a single-element container that is normally created empty, and can only be set once. The I in `IVar` stands for immutable. Reading an `IVar` normally blocks until it is set. It is safe to set and read an `IVar` from different threads.

If you want to have some parallel task set the value in an ‘IVar`, you want a `Future`. If you want to create a graph of parallel tasks all executed when the values they depend on are ready you want `dataflow`. `IVar` is generally a low-level primitive.

## Examples

Create, set and get an ‘IVar`

“‘ruby ivar = Concurrent::IVar.new ivar.set 14 ivar.value #=> 14 ivar.set 2 # would now be an error “`

## See Also

  1. For the theory: Arvind, R. Nikhil, and K. Pingali. [I-Structures: Data structures for parallel computing](dl.acm.org/citation.cfm?id=69562). In Proceedings of Workshop on Graph Reduction, 1986.

  2. For recent application: [DataDrivenFuture in Habanero Java from Rice](www.cs.rice.edu/~vs3/hjlib/doc/edu/rice/hj/api/HjDataDrivenFuture.html).

Public Class Methods

new(value = NULL, opts = {}, &block) click to toggle source

Create a new ‘IVar` in the `:pending` state with the (optional) initial value.

@param [Object] value the initial value @param [Hash] opts the options to create a message with @option opts [String] :dup_on_deref (false) call ‘#dup` before returning

the data

@option opts [String] :freeze_on_deref (false) call ‘#freeze` before

returning the data

@option opts [String] :copy_on_deref (nil) call the given ‘Proc` passing

the internal value and returning the value returned from the proc
Calls superclass method
# File lib/concurrent-ruby/concurrent/ivar.rb, line 62
def initialize(value = NULL, opts = {}, &block)
  if value != NULL && block_given?
    raise ArgumentError.new('provide only a value or a block')
  end
  super(&nil)
  synchronize { ns_initialize(value, opts, &block) }
end

Public Instance Methods

add_observer(observer = nil, func = :update, &block) click to toggle source

Add an observer on this object that will receive notification on update.

Upon completion the ‘IVar` will notify all observers in a thread-safe way. The `func` method of the observer will be called with three arguments: the `Time` at which the `Future` completed the asynchronous operation, the final `value` (or `nil` on rejection), and the final `reason` (or `nil` on fulfillment).

@param [Object] observer the object that will be notified of changes @param [Symbol] func symbol naming the method to call when this

`Observable` has changes`
# File lib/concurrent-ruby/concurrent/ivar.rb, line 81
def add_observer(observer = nil, func = :update, &block)
  raise ArgumentError.new('cannot provide both an observer and a block') if observer && block
  direct_notification = false

  if block
    observer = block
    func = :call
  end

  synchronize do
    if event.set?
      direct_notification = true
    else
      observers.add_observer(observer, func)
    end
  end

  observer.send(func, Time.now, self.value, reason) if direct_notification
  observer
end

Protected Instance Methods

complete(success, value, reason) click to toggle source

@!visibility private

# File lib/concurrent-ruby/concurrent/ivar.rb, line 177
def complete(success, value, reason)
  complete_without_notification(success, value, reason)
  notify_observers(self.value, reason)
  self
end
complete_without_notification(success, value, reason) click to toggle source

@!visibility private

# File lib/concurrent-ruby/concurrent/ivar.rb, line 184
def complete_without_notification(success, value, reason)
  synchronize { ns_complete_without_notification(success, value, reason) }
  self
end
fail(reason = StandardError.new) click to toggle source

@!macro ivar_fail_method

Set the `IVar` to failed due to some error and wake or notify all threads waiting on it.

@param [Object] reason for the failure
@raise [Concurrent::MultipleAssignmentError] if the `IVar` has already
  been set or otherwise completed
@return [IVar] self
# File lib/concurrent-ruby/concurrent/ivar.rb, line 135
def fail(reason = StandardError.new)
  complete(false, nil, reason)
end
notify_observers(value, reason) click to toggle source

@!visibility private

# File lib/concurrent-ruby/concurrent/ivar.rb, line 190
def notify_observers(value, reason)
  observers.notify_and_delete_observers{ [Time.now, value, reason] }
end
ns_complete_without_notification(success, value, reason) click to toggle source

@!visibility private

# File lib/concurrent-ruby/concurrent/ivar.rb, line 195
def ns_complete_without_notification(success, value, reason)
  raise MultipleAssignmentError if [:fulfilled, :rejected].include? @state
  set_state(success, value, reason)
  event.set
end
ns_initialize(value, opts) { || ... } click to toggle source

@!visibility private

# File lib/concurrent-ruby/concurrent/ivar.rb, line 155
def ns_initialize(value, opts)
  value = yield if block_given?
  init_obligation
  self.observers = Collection::CopyOnWriteObserverSet.new
  set_deref_options(opts)

  @state = :pending
  if value != NULL
    ns_complete_without_notification(true, value, nil)
  end
end
safe_execute(task, args = []) { |success, val, reason| ... } click to toggle source

@!visibility private

# File lib/concurrent-ruby/concurrent/ivar.rb, line 168
def safe_execute(task, args = [])
  if compare_and_set_state(:processing, :pending)
    success, val, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args)
    complete(success, val, reason)
    yield(success, val, reason) if block_given?
  end
end
set(value = NULL) { || ... } click to toggle source

@!macro ivar_set_method

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

@!macro ivar_set_parameters_and_exceptions
  @param [Object] value the value to store in the `IVar`
  @yield A block operation to use for setting the value
  @raise [ArgumentError] if both a value and a block are given
  @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already
    been set or otherwise completed

@return [IVar] self
# File lib/concurrent-ruby/concurrent/ivar.rb, line 113
def set(value = NULL)
  check_for_block_or_value!(block_given?, value)
  raise MultipleAssignmentError unless compare_and_set_state(:processing, :pending)

  begin
    value = yield if block_given?
    complete_without_notification(true, value, nil)
  rescue => ex
    complete_without_notification(false, nil, ex)
  end

  notify_observers(self.value, reason)
  self
end
try_set(value = NULL, &block) click to toggle source

Attempt to set the ‘IVar` with the given value or block. Return a boolean indicating the success or failure of the set operation.

@!macro ivar_set_parameters_and_exceptions

@return [Boolean] true if the value was set else false

# File lib/concurrent-ruby/concurrent/ivar.rb, line 145
def try_set(value = NULL, &block)
  set(value, &block)
  true
rescue MultipleAssignmentError
  false
end