Class: Functional::Delay

Inherits:
Object
  • Object
show all
Defined in:
lib/functional/delay.rb

Overview

Note:

This is a write-once, read-many, thread safe object that can be used in concurrent systems. Thread safety guarantees cannot be made about objects contained within this object, however. Ruby variables are mutable references to mutable objects. This cannot be changed. The best practice it to only encapsulate immutable, frozen, or thread safe objects. Ultimately, thread safety is the responsibility of the programmer.

Lazy evaluation of a block yielding an immutable result. Useful for expensive operations that may never be needed.

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.

See Also:

Since:

  • 1.0.0

Instance Method Summary collapse

Constructor Details

#initialize { ... } ⇒ Delay

Create a new Delay in the :pending state.

Yields:

  • the delayed operation to perform

Raises:

  • (ArgumentError)

    if no block is given

Since:

  • 1.0.0


39
40
41
42
43
44
# File 'lib/functional/delay.rb', line 39

def initialize(&block)
  raise ArgumentError.new('no block given') unless block_given?
  @mutex = Mutex.new
  @state = :pending
  @task  = block
end

Instance Method Details

#fulfilled?Boolean Also known as: value?

Has the delay been fulfilled?

Returns:

  • (Boolean)

Since:

  • 1.0.0


91
92
93
# File 'lib/functional/delay.rb', line 91

def fulfilled?
  state == :fulfilled
end

#pending?Boolean

Is delay completion still pending?

Returns:

  • (Boolean)

Since:

  • 1.0.0


105
106
107
# File 'lib/functional/delay.rb', line 105

def pending?
  state == :pending
end

#reasonStandardError

The exception raised when processing the block. Returns nil if the operation is still :pending or has been :fulfilled.

Returns:

  • (StandardError)

    the exception raised when processing the block else nil.

Since:

  • 1.0.0


61
62
63
64
65
66
# File 'lib/functional/delay.rb', line 61

def reason
  @mutex.lock
  @reason
ensure
  @mutex.unlock
end

#rejected?Boolean Also known as: reason?

Has the delay been rejected?

Returns:

  • (Boolean)

Since:

  • 1.0.0


98
99
100
# File 'lib/functional/delay.rb', line 98

def rejected?
  state == :rejected
end

#stateSymbol

Current state of block processing.

Returns:

  • (Symbol)

    the current state of block processing

Since:

  • 1.0.0


49
50
51
52
53
54
# File 'lib/functional/delay.rb', line 49

def state
  @mutex.lock
  @state
ensure
  @mutex.unlock
end

#valueObject

Return the (possibly memoized) value of the delayed operation.

If the state is :pending then the calling thread will block while the operation is performed. All other threads simultaneously calling #value will block as well. Once the operation is complete (either :fulfilled or :rejected) all waiting threads will unblock and the new value will be returned.

If the state is not :pending when #value is called the (possibly memoized) value will be returned without blocking and without performing the operation again.

Returns:

  • (Object)

    the (possibly memoized) result of the block operation

Since:

  • 1.0.0


81
82
83
84
85
86
87
# File 'lib/functional/delay.rb', line 81

def value
  @mutex.lock
  execute_task_once
  @value
ensure
  @mutex.unlock
end