Acme-Parataxis
view release on metacpan or search on metacpan
lib/Acme/Parataxis/Supervisor.pm view on Meta::CPAN
use v5.40;
no warnings qw[experimental::class recursion];
use feature 'class';
#
# OTP-style supervisor trees: children are supervised actors (or nested supervisors) that are
# restarted whenever they die, according to a restart strategy, until a restart budget runs out -
# at which point the tree is torn down and run() fails with an aggregate
# Acme::Parataxis::Error::Supervisor. This is the "heal-fast" half of supervision's "supervision is
# deliberately out of scope" guardrail, and it is pure Perl on top of the existing park/wake
# machinery: one supervision fiber owns the tree and learns a child died through that child's own
# teardown (an actor's on_death hook, or a wrapper fiber around a nested supervisor's run).
class Acme::Parataxis::Supervisor v0.1.3 {
use Acme::Parataxis qw[fiber];
use Acme::Parataxis::Channel;
use Acme::Parataxis::Error;
use builtin qw[blessed];
use Time::HiRes qw[time];
use Carp qw[croak];
# Restart strategy. OneForOne restarts only the child that died; OneForAll restarts every child
# (they share state the death may have broken); RestForOne restarts the dead child and
# everything started after it. max_restarts deaths within `within` seconds exhaust the budget:
# the (max_restarts+1)-th death inside the window fails the supervisor instead of restarting.
field $strategy : reader : param = 'OneForOne';
field $max_restarts : reader : param = 5;
field $within : reader : param = 60;
# Supervised children in start order. Each is a spec ({name, factory, initial}) plus the live
# bookkeeping for the instance currently running: `instance`, the `token` identifying that
# attempt, `pending` (an event for it is still expected), `restarts`, and `index`.
field @children;
# The original supervise() arguments, so a restarted supervisor can rebuild its own subtree
# from scratch instead of inheriting the dead one's instances.
field @specs;
# Restart budget: [ when, child name, reason ] for every restart still inside the window.
field @budget;
# Where dying instances report [ $child, $token, $err ]. Oversized so a report can never park a
# fiber that is in the middle of tearing itself down.
field $deaths;
field $running : reader = false;
field $stopping : reader = false;
field $started : reader = false;
ADJUST {
croak "Supervisor->new: strategy must be OneForOne, OneForAll or RestForOne (got '$strategy')"
unless defined $strategy && ( $strategy eq 'OneForOne' || $strategy eq 'OneForAll' || $strategy eq 'RestForOne' );
croak 'Supervisor->new: max_restarts must be a non-negative integer' unless defined $max_restarts && $max_restarts =~ /\A\d+\z/;
croak 'Supervisor->new: within must be a non-negative number of seconds' unless defined $within && $within =~ /\A[0-9]+(?:\.[0-9]+)?\z/;
$deaths = Acme::Parataxis::Channel->new( capacity => 4096 );
}
# Register a child. $thing is an Actor (adopted as-is for the first run, restarted with a fresh
# mailbox), a Supervisor (the same, for a subtree), or a CODE ref factory returning either -
# which is what you want when the child should not exist until the tree starts. The optional
# name defaults to child_1, child_2, ... in registration order and must be unique.
method supervise ( $thing, %opts ) {
croak 'Supervisor->supervise() may not add children once run() has started' if $started;
croak 'Supervisor->supervise() requires an Acme::Parataxis::Actor, an Acme::Parataxis::Supervisor, or a CODE ref factory'
unless ref $thing eq 'CODE' ||
( blessed($thing) && ( $thing->isa('Acme::Parataxis::Actor') || $thing->isa('Acme::Parataxis::Supervisor') ) );
my $unknown = join ', ', grep { $_ ne 'name' } sort keys %opts;
croak "Supervisor->supervise(): unknown options: $unknown" if length $unknown;
my $name = defined $opts{name} ? $opts{name} : 'child_' . ( scalar(@children) + 1 );
( run in 1.281 second using v1.01-cache-2.11-cpan-b554fb43c41 )