NAME

Acme::Parataxis::Signal - One-Shot Event Flags with Waiter Queues

SYNOPSIS

use v5.40;
use Acme::Parataxis qw[async fiber];
use Acme::Parataxis::Signal;

# Initialize unsignalled (count => false):
my $ready = Acme::Parataxis::Signal->new( count => false );

async {
    fiber {
        say 'Worker: waiting for ready signal...';
        $ready->wait;    # parks until signalled
        say 'Worker: unblocked and running!';
    };

    say 'Main: completing setup...';
    $ready->send;        # wakes the worker fiber
};

DESCRIPTION

An Acme::Parataxis::Signal represents a single notification flag accompanied by a FIFO queue of waiters. Calling send signals the event, waking a parked fiber or saving the notification for the next fiber that arrives.

A key design feature of signals is early firing safety: if send is called before any fiber has called wait, the signal is remembered. When a fiber eventually calls wait, it consumes the remembered signal and proceeds immediately without parking. This eliminates initialization race conditions where a notifier fires before its listeners are spawned.

  • send() releases one waiter, or records the signal if no waiters exist.

  • broadcast() wakes all currently queued waiters simultaneously, clearing any remembered signal.

METHODS

new( [ count => $bool = true ] )

my $ready = Acme::Parataxis::Signal->new( count => false );   # unsignalled (common case)
my $fired = Acme::Parataxis::Signal->new;                     # pre-signalled

Constructs a new signal.

Important Default: count defaults to true (pre-signalled). In this state, the first wait() will not block. For the standard pattern where waiters must park until an event occurs, pass count => false.

send( )

$signal->send;

Delivers the signal to the longest-waiting entity (waking a parked fiber or executing a registered callback). If no waiters are currently queued, the signal is stored (setting count to true) so the next wait() will return immediately. Returns true.

broadcast( )

$signal->broadcast;

Wakes every currently queued waiter in a single pass. Unlike send(), an unconsumed broadcast is not remembered: if no waiters are present, the notification is discarded, and any previously banked signal is cleared (resetting count to false). Returns true.

wait( [$callback] )

$signal->wait;            # parks the calling fiber until signalled
$signal->wait(\&on_done); # registers a callback without parking
  • Without arguments: If a signal is already pending, consumes it (resetting count to false) and returns immediately. Otherwise, parks the calling fiber until a signal arrives. Always returns undef. Must run inside a scheduled fiber unless a signal is already pending.

  • With a CODE reference: Registers $callback to run when the signal fires, returning immediately without parking. If a signal is already pending, the callback executes inline before wait() returns. Safe to call outside a fiber.

awaited( )

my $num_waiters = $signal->awaited;

Returns the total count of currently queued waiters (parked fibers plus registered callbacks). Returns 0 when no waiters are waiting.

count( )

Returns true if a signal has been banked and is currently pending consumption; false otherwise.

remove_waiter( $waiter )

$signal->remove_waiter($cb_or_fid);

Removes a specific callback or fiber ID from the wait queue without triggering it. Used during cancellation and cleanup.

Recipes

Fan-out broadcast notification

Use broadcast() to release multiple concurrent workers on a single event:

async {
    my $start_signal = Acme::Parataxis::Signal->new( count => false );

    for my $id ( 1 .. 3 ) {
        fiber {
            $start_signal->wait;
            say "Worker $id running!";
        };
    }

    await_sleep(50);
    say "Starting all workers:";
    $start_signal->broadcast;    # unblocks all 3 workers at once
};

Non-blocking callback triggers

Register callbacks to execute immediately upon notification without spawning intermediate fibers:

my $done = Acme::Parataxis::Signal->new( count => false );

$done->wait( sub {
    say "Cleanup callback executed inline on send()";
} );

# Later, in any fiber:
$done->send;

Behavior Notes

broadcast( ) cancels banked signals

broadcast() clears any pending signal flag even if no waiters were queued. If you need to clear the wait queue while preserving a banked signal, invoke send() repeatedly for each waiter instead.

Single wake-up per send( )

Calling send() multiple times while no fibers are waiting will only bank a single notification. The signal records that it fired, not how many times it fired. For event counting, use Acme::Parataxis::Semaphore or Acme::Parataxis::Channel.

Inline callback execution

Callbacks registered with wait(\&cb) run synchronously inside send() or broadcast(), in the context of the signaling fiber. If a registered callback dies, the exception propagates directly out of send(), interrupting the broadcast loop and stranding any remaining queued waiters.

SEE ALSO

Acme::Parataxis, Acme::Parataxis::Semaphore, Acme::Parataxis::Future

AUTHOR

Sanko Robinson https://github.com/sanko

LICENSE

Copyright (C) Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the terms found in the Artistic License 2.