Class: Farce::Signal

Inherits:
Object
  • Object
show all
Includes:
Internal::Noncopyable, Farce::Shareable::Unfreezable, Internal::Inspect
Defined in:
lib/farce/signal.rb

Overview

A Ractor-shareable notification signal for changes to separately synchronized state. Broadcast after updating the state. Waiters check that state again after each notification. A broadcast wakes all current waiters and advances the signal's generation.

Examples:

Waiting for a resource

signal    = Farce::Signal.new
resources = Farce::Strict::Queue.new
producer  = Thread.new do
  resources.push(:resource)
  signal.broadcast
end
resource = signal.wait_until(timeout: 1.0) { resources.try_pop }
producer.join
resource # => :resource, or nil if the timeout expires

Instance Method Summary collapse

Methods included from Shareable

#ractor_shareable?

Methods included from Internal::Noncopyable

#duplicable?

Constructor Details

#initialize ⇒ Signal

Returns a new instance of Signal.



26
27
28
29
# File 'lib/farce/signal.rb', line 26

def initialize
  @signal = Internal::Signal.new
  super
end

Instance Method Details

#broadcast ⇒ Integer

Advance the generation and wake all current waiters.

Returns:

  • (Integer) —

    the new generation



37
# File 'lib/farce/signal.rb', line 37

def broadcast = @signal.broadcast

#generation ⇒ Integer

Read the current generation. Take this snapshot before checking external state.

Returns:

  • (Integer) —

    the current generation, initially zero



33
# File 'lib/farce/signal.rb', line 33

def generation = @signal.generation

#num_waiting ⇒ Integer

Returns the number of callers currently waiting.

Returns:

  • (Integer) —

    the number of callers currently waiting



40
# File 'lib/farce/signal.rb', line 40

def num_waiting = @signal.num_waiting

#wait(observed = nil, timeout: nil) { ... } ⇒ Integer, ...

Wait for the generation to change. Without a snapshot, wait for the next broadcast. Return immediately if the supplied generation has already changed.

Parameters:

  • observed (Integer, nil) (defaults to: nil) —

    a generation snapshot, or nil to take a snapshot now

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum seconds to wait, or nil to wait indefinitely

Yields:

  • called without arguments when the timeout expires

Returns:

  • (Integer, BasicObject, nil) —

    the changed generation, or the fallback result or nil on timeout



48
# File 'lib/farce/signal.rb', line 48

def wait(observed = nil, timeout: nil, &) = @signal.wait(observed, timeout:, &)

#wait_until(timeout: nil) { ... } ⇒ BasicObject?

Check a condition immediately, then check again after broadcasts until it succeeds. Take a generation snapshot before every check so a broadcast during the check is not missed. The block runs in the caller without holding a signal lock. Synchronize access to shared state inside the block, and claim the resource there if another waiter could consume it.

One timeout budget covers all checks and waits. The block is not interrupted when time expires. A timeout of zero checks once without waiting. Exceptions from the block propagate to the caller.

Parameters:

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available, or nil to wait indefinitely

Yields:

  • checks or claims the resource

Yield Returns:

  • (BasicObject, nil, false) —

    a truthy result on success, or nil or false to keep waiting

Returns:

  • (BasicObject, nil) —

    the first truthy block result, or nil on timeout

Raises:

  • (LocalJumpError) —

    if no block is given

  • (ArgumentError) —

    if the timeout is negative or not finite



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/farce/signal.rb', line 76

def wait_until(timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  Internal.with_timeout(timeout) do |_, deadline|
    observed = generation
    result   = yield
    return result if result

    remaining = deadline - Clock.now if deadline
    return if remaining && !remaining.positive?
    return unless wait(observed, timeout: remaining)
  end
end

#wait_while(timeout: nil) { ... } ⇒ true?

Check a condition immediately, then after broadcasts while it remains truthy. Uses the same timeout budget and notification handling as #wait_until.

Parameters:

  • timeout (Numeric, nil) (defaults to: nil) —

    the total seconds available, or nil to wait indefinitely

Yields:

  • checks the condition

Yield Returns:

  • (BasicObject) —

    a truthy value to keep waiting, or nil or false to stop

Returns:

  • (true, nil) —

    true when the condition becomes false, or nil on timeout

Raises:

  • (LocalJumpError) —

    if no block is given



57
58
59
60
# File 'lib/farce/signal.rb', line 57

def wait_while(timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  wait_until(timeout:) { !yield }
end