NAME
Acme::Parataxis::Nursery - Structured Concurrency Container for Scoped Fiber Lifecycles
SYNOPSIS
use v5.40;
use Acme::Parataxis qw[async nursery];
async {
my ( $user, $preferences );
my $ok = eval {
nursery( sub ($n) {
$n->spawn( sub { $user = fetch_user(123) } );
$n->spawn( sub { $preferences = fetch_prefs(123) } );
} );
1;
};
die $@ unless $ok;
# Both children are guaranteed to have completed:
say "User: " . $user->name;
};
DESCRIPTION
Acme::Parataxis::Nursery implements structured concurrency (popularized by Trio and Go errgroups). When concurrent tasks are executed inside a nursery, the lifetimes of all child fibers are bounded by the enclosing block:
Guaranteed join:
nursery( sub ($n) { ... } )does not return until every child spawned via$n->spawnhas completely terminated.Prompt sibling cancellation: If any child fiber dies with an unhandled exception, the nursery immediately cancels all surviving siblings via an internal cancellation token. The nursery drains all in-flight work before rethrowing the culprit failure.
No orphaned fibers: Even if the parent fiber is interrupted (e.g. by an outer
with_timeout), all children in the nursery are cancelled and drained before the parent unwinds.
Fibers spawned independently via the top-level fiber { ... } or spawn() inside the block are not enrolled in the nursery and do not delay its join.
METHODS
spawn( sub { ... } )
my $child = $n->spawn( sub {
do_concurrent_work();
} );
Spawns a child fiber enrolled in this nursery. Returns the Acme::Parataxis fiber object.
Children park once at birth rather than executing synchronously inline. This guarantees that an immediate failure on the child's first statement routes through the scheduler's aggregation machinery rather than throwing unexpectedly into the parent's setup block.
Croaks if called outside a scheduled fiber or without a code reference.
token( )
my $token = $n->token;
Returns the nursery's internal Acme::Parataxis::CancellationToken. Cancelling this token manually cancels all enrolled children and drains the group. Passing this token into a nested with_timeout binds the group to an external deadline.
ERROR HANDLING
Acme::Parataxis::Error::Nursery
When one or more child fibers die, the nursery drains all remaining siblings and throws an Acme::Parataxis::Error::Nursery exception:
$err->primary: Returns the first natural failure (the real bug or exception that caused the nursery to fail, ignoring subsequentCancellederrors thrown by siblings as they were unwound).$err->failures: Returns the list of every exception thrown by any child in the nursery, in spawn order.
use v5.40;
use Acme::Parataxis qw[async nursery await_sleep];
async {
eval {
nursery( sub ($n) {
$n->spawn( sub { die "Database connection lost\n" } );
$n->spawn( sub { await_sleep(10_000) } );
} );
};
if ( my $err = $@ ) {
if ( ref $err && $err->isa('Acme::Parataxis::Error::Nursery') ) {
say "Root cause: " . $err->primary; # "Database connection lost"
say "Total child errors: " . scalar( $err->failures ); # 2 (database error + cancelled sleep)
}
}
};
Recipes
Concurrent scatter-gather
To collect data from children, write into variables in the parent's lexical scope:
my ( $orders, $inventory, $shipping );
nursery( sub ($n) {
$n->spawn( sub { $orders = load_orders($id) } );
$n->spawn( sub { $inventory = check_stock($id) } );
$n->spawn( sub { $shipping = calc_rates($id) } );
} );
# All three lookups are complete and successful here.
Setting a deadline on the entire nursery
Pass the nursery's token to with_timeout:
use Acme::Parataxis qw[async nursery with_timeout await_sleep];
async {
eval {
nursery( sub ($n) {
$n->spawn( sub { await_sleep(5000) } );
$n->spawn( sub { await_sleep(5000) } );
# If 500ms elapses, the token cancels and aborts both children:
with_timeout( 500, $n->token, sub { await_sleep(10_000) } );
} );
};
warn "Nursery timed out: $@" if $@;
};
Behavior Notes
Block returns do not short-circuit children
The block passed to
nursery()cannot return early to bypass pending children. The nursery joins every child spawned via$n->spawnbefore returning. The nursery's return value is the return value of the enclosed block, delivered only when all children have succeeded.Fail-fast vs. best-effort
A nursery enforces all-or-nothing execution. The moment one child fails, all surviving siblings are cancelled. If you need independent background tasks where failures do not cascade, use unmanaged
fiber { ... }instances combined with Acme::Parataxis::Monitor instead.Parent exceptions take priority
If the parent block itself throws an exception before spawning finishes, all currently enrolled children are immediately cancelled and drained, and the parent's own exception is rethrown verbatim (not wrapped in an
Error::Nursery).Nested nurseries are isolated
Nesting a
nurseryinside another forms an independent hierarchy. A failure in an inner nursery cancels only its own children; if the inner nursery's error is caught with aneval, the outer nursery continues uninterrupted.
SEE ALSO
Acme::Parataxis, Acme::Parataxis::CancellationToken, Acme::Parataxis::Error, 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.