NAME

Acme::Parataxis::Actor - A Fiber with a Mailbox

SYNOPSIS

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

async {
    my $upper = Acme::Parataxis::Actor->spawn( sub ( $self, $msg ) {
        return uc $msg->{text};
    } );

    # ask: queue a message and get a future for the handler's answer
    say $upper->ask( { text => 'hello' } )->await;    # HELLO

    # send: queue a message, expect no answer
    $upper->send( { text => 'nobody is reading this' } );

    # handle what is queued, then shutdown gracefully
    $upper->stop;
};

DESCRIPTION

An actor is a private fiber with an incoming mailbox. spawn starts one, binds it to a handler, and returns immediately. The handler runs inside the actor's private fiber, processing one message at a time. Because messages are serialized and isolated to this single fiber, internal state requires no mutexes or locking: keep your variables inside the closure and interact with them purely via messages.

Two verbs do the talking:

  • ask( $msg ) queues a message and returns an Acme::Parataxis::Future that resolves with the handler's return value.

  • send( $msg ) queues a message and returns immediately without waiting for an answer.

The handler is invoked as sub ( $self, $msg ) { ... }. It may perform any standard cooperative action—calling await_sleep, reading from channels, awaiting other actors, or acquiring locks—and the actor will wait for it to return before taking the next message.

The mailbox is a bounded Acme::Parataxis::Channel. If producers send messages faster than the handler can process them, the mailbox fills and subsequent senders park. Because this is a standard fiber wait, round-trips can be bounded with with_timeout or cancellation tokens.

Actors can be registered globally by name, hot-swapped while running (swap), and supervised under Acme::Parataxis::Supervisor.

METHODS

spawn( $code, [$capacity = 16], [%opts] )

my $actor = Acme::Parataxis::Actor->spawn(
    sub ( $self, $msg ) { ... },
    32,
    supervised => 1,
    name       => 'worker'
);

Class factory that spawns and starts a new actor. The handler receives ($self, $msg).

  • $capacity

    Mailbox queue capacity before send and ask park the caller (default 16). Must be >= 1.

  • supervised => 1

    If true, an uncaught exception in the handler terminates the actor so a supervisor can observe the failure and restart it. If false, an exception only fails that specific message's ask and the actor continues running.

  • name => $name

    Registers the actor in the process-wide name table so it can be retrieved via Acme::Parataxis->actor($name). Must be unique among live actors.

ask( $msg )

my $future = $actor->ask( { cmd => 'status' } );
my $reply  = $future->await;

Enqueues $msg and returns an Acme::Parataxis::Future. If the mailbox is full, the calling fiber parks until space is available. When the handler finishes, the future resolves with its return value. If the handler dies, the future fails with that error.

send( $msg )

$actor->send( { event => 'ping' } );

Enqueues $msg without waiting for a reply. Parks if the mailbox is full. Returns true once queued.

stop( )

$actor->stop;

Queues a graceful shutdown sentinel behind all currently pending messages. The actor continues processing queued work and exits once the mailbox drains. From the moment stop is called, new ask or send calls croak. Idempotent.

is_alive( )

Returns true if the actor's fiber is running or draining; returns false once teardown has completed.

on_death( sub ($actor, $error) { ... } )

$actor->on_death( sub ( $actor, $err ) {
    warn "Actor died: $err" if $err;
} );

Registers a callback that fires once when the actor terminates. $error contains the exception that killed the actor, or undef if it stopped cleanly. If the actor is already dead, the callback runs immediately.

swap( sub ($self, $msg) { ... } )

$actor->swap( sub ( $self, $msg ) { ... } );

Atomically replaces the message handler for subsequent messages. The swap takes effect at the next message boundary; any message currently being executed finishes under the previous handler.

respawn( )

my $clone = $actor->respawn;

Spawns a fresh actor with the same handler, mailbox capacity, and options. The new actor receives an empty mailbox; any unanswered requests on the old actor fail on the old actor. Used by supervisors to restart crashed workers.

name( )

Returns the registered name string, or undef if unnamed.

error( )

Returns the exception that terminated the actor, or undef if still running or stopped cleanly.

fid( )

Returns the fiber ID of the actor's message-processing loop.

THE NAME REGISTRY

Acme::Parataxis->actor( $name ) / Acme::Parataxis->whereis( $name )

my $srv = Acme::Parataxis->actor('logger');

Looks up a live actor registered with name => $name. Returns the actor object or undef. Registering an actor holds a strong reference to it, keeping it alive until explicitly stopped even if all external lexical references are dropped.

Recipes

State encapsulation without locks

my $counter = Acme::Parataxis::Actor->spawn( sub ( $self, $msg ) {
    state $count = 0;
    if    ( $msg eq 'inc' ) { return ++$count }
    elsif ( $msg eq 'get' ) { return $count   }
} );

$counter->send('inc');
say $counter->ask('get')->await;    # 1

Awaiting complete shutdown

stop() queues a shutdown, but returns immediately. To wait for queued messages to completely drain:

use Acme::Parataxis::Monitor;

$actor->stop;
Acme::Parataxis::Monitor->new( target => $actor )->await;
say 'Actor has completely finished draining';

Behavior Notes

  • Failure isolation

    On an unsupervised actor (the default), an exception inside the handler fails the corresponding ask future (or prints a warning if sent via send), but the actor remains alive to handle subsequent messages. Under supervised => 1, a handler die terminates the actor, unregisters its name, fails all pending queued asks, and notifies supervisors.

  • Deadlock avoidance

    An actor cannot ask itself. Awaiting its own response deadlocks the actor's single message loop. Use send for self-messages.

SEE ALSO

Acme::Parataxis, Acme::Parataxis::Supervisor, Acme::Parataxis::Channel, Acme::Parataxis::Future, Acme::Parataxis::Monitor

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.