NAME
Acme::Parataxis::Blocking - Run CPU-Bound Perl Closures on Background Interpreters or Forked Children
SYNOPSIS
use v5.40;
use Acme::Parataxis qw[async fiber await_sleep];
use Acme::Parataxis::Blocking qw[spawn_blocking spawn_blocking_fork kill_blocking_fork];
async {
# Offload CPU-heavy computation to a background thread interpreter:
my $future = spawn_blocking( sub ($data) {
return heavy_cpu_digest($data);
}, $payload );
# Other fibers continue running smoothly while the background thread computes
fiber {
await_sleep(10);
say 'Cooperative fibers are still running!';
};
my $result = $future->await;
say "Digest: $result";
};
DESCRIPTION
The Acme::Parataxis native C worker pool handles blocking I/O and sleeps, but it cannot run arbitrary Perl code without blocking the main interpreter. Acme::Parataxis::Blocking offloads heavy, CPU-bound Perl closures to separate execution contexts so your cooperative fibers never drop frames or stall.
Two backends are provided:
spawn_blocking: Runs the closure on an isolated background Perl interpreter using native threads (threads->create). Requires a Perl built with ithreads (useithreads). Thread dependencies (threads,threads::shared,Thread::Queue) are loaded lazily on the first invocation.spawn_blocking_fork: Runs the closure in a forked child process, marshaling arguments and results over a pipe using Storable. Works on non-threaded Perls (POSIX platforms whered_forkis defined).
Both functions return an Acme::Parataxis::Future that can be awaited, combined with wait_all/wait_any, or bounded with with_timeout.
FUNCTIONS
spawn_blocking( $code, [@args] )
my $future = spawn_blocking( sub ($x, $y) {
return $x ** $y;
}, 2, 64 );
Executes $code on an isolated background Perl interpreter. @args are passed to the closure.
Copy-in / Copy-out: The closure receives a snapshot of its arguments and captured lexicals. Memory is not shared with the parent interpreter; in-place mutations in the worker do not affect the caller.
Return values: The result (or exception) is serialized back via
threads::shared::shared_clone. Plain scalars, arrayrefs, and hashrefs cross safely. Non-serializable structures (such as CODE refs or open handles) cause the future to fail with an error.No scheduler access: The worker closure runs in an isolated interpreter and must not invoke
Acme::Parataxisscheduling primitives.
Concurrence is capped by max_blocking_threads(). Calls exceeding capacity park the calling fiber until a slot frees. Must be called within a scheduled fiber and under a real-time scheduler (croaks under virtual => 1).
set_max_blocking_threads( $count )
set_max_blocking_threads(8);
Sets the maximum number of concurrent background interpreter threads (default 4, or overridden by the PARATAXIS_SB_THREADS environment variable). Must be called before the first spawn_blocking invocation.
max_blocking_threads( )
Returns the current thread concurrency limit.
spawn_blocking_fork( $code, [@args] )
my $future = spawn_blocking_fork( sub ($n) {
return compute_pi_digits($n);
}, 10_000 );
Forks a child process to run $code. Arguments and results are marshaled over an internal pipe using Storable.
Unlike thread workers, forked workers can be cancelled and terminated midway through execution via kill_blocking_fork().
Not available on Windows (where Perl's fork() is an emulation over threads) or platforms without fork(2). Croaks under run( virtual => 1 ).
kill_blocking_fork( $future, [$signal = 'TERM'] )
kill_blocking_fork( $future, 'KILL' );
Delivers $signal to the child process associated with $future. Returns true if the signal was delivered, or false if the child was already reaped. The future resolves with an error indicating termination.
set_max_blocking_forks( $count )
set_max_blocking_forks(8);
Sets the maximum number of concurrent forked child processes (default 4, or overridden by PARATAXIS_SBF_FORKS). Must be called before the first spawn_blocking_fork invocation.
max_blocking_forks( )
Returns the current fork concurrency limit.
Recipes
Offloading long-running calculations
use Acme::Parataxis qw[async await];
use Acme::Parataxis::Blocking qw[spawn_blocking];
async {
my $task = spawn_blocking( sub {
my $sum = 0;
$sum += $_ for 1 .. 100_000_000;
return $sum;
} );
say 'Calculating in background...';
my $total = await($task);
say "Total: $total";
};
Cancelling runaway background work
Native threads cannot be forcibly killed, but forked processes can:
use Acme::Parataxis qw[async fiber await_sleep];
use Acme::Parataxis::Blocking qw[spawn_blocking_fork kill_blocking_fork];
async {
my $f = spawn_blocking_fork( sub { 1 while 1 } );
fiber {
await_sleep(500);
say 'Work took too long; killing child...';
kill_blocking_fork($f);
};
my $err = eval { $f->await } ? undef : $@;
say "Stopped with error: $err";
};
PLATFORM WARNING
Strawberry Perl / Win32 ithreads crashes
On certain Perl 5.42.x builds on Windows, calling
threads->createtriggers aperl_clonesegmentation fault if classes with amethod DESTROYinside afeature 'class'block have been compiled. In addition, loading specific namespaces (includingAcme::Parataxis::SyncorAcme::Parataxis::TVar) can trigger this crash. If targeting Windows withspawn_blocking, run compute tasks before loading sync modules, or use non-class fallback guards.Windows socket cloning stall
Under Windows, while any fiber is parked in
await_readorawait_writeon a socket, invokingthreads->createstalls until the socket wait returns. This occurs because Windows holds descriptor duplication during outstanding socket operations. Schedulespawn_blockingcalls before initiating socket loops to avoid latency spikes.
spawn_blocking_fork does not use threads->create and is unaffected by these issues (though it is not available on Windows).
SEE ALSO
Acme::Parataxis, Acme::Parataxis::Future, threads, Storable
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.