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).
$capacityMailbox queue capacity before
sendandaskpark the caller (default 16). Must be>= 1.supervised => 1If 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
askand the actor continues running.name => $nameRegisters 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
askfuture (or prints a warning if sent viasend), but the actor remains alive to handle subsequent messages. Undersupervised => 1, a handler die terminates the actor, unregisters its name, fails all pending queued asks, and notifies supervisors.Deadlock avoidance
An actor cannot
askitself. Awaiting its own response deadlocks the actor's single message loop. Usesendfor 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.