If you're reading this, mostly everything from previous chapters has been implemented.
Backlog
Leftover half-formed concepts and implementations from the first roadmap. Some of them are just kinda beyond the amount of planning I've done so far or require changes to the core (libparataxis? What am I calling it?). Keeping them here so nothing is lost.
with_cancel- cancellation scopes (register/unregister groups of waits as one unit).defer- run-cleanup-on-every-exit (like Go'sdeferfor fiber exit paths).- Monitor & linked death notification (observe another fiber's death without owning it).
- Actor hot-code swap + named registry.
with_timeoutre-entrancy polish.Channel->new( timeout => $ms )- per-channel default wait timeout forget/put, straight from #7's CSP sketch (Channel->new( capacity => 1024, timeout => 500, ... )). Never implemented; today the only channel timeout isselect'stimeoutoption. A channel-leveltimeoutwould parkget/put/try_*with that deadline and croakError::Timeouton expiry.- Note:
select'sdefaultarm, the main-loop driver hook, and channel combinators (map/filter/merge) all shipped.
Next roadmap
Now that the core primitives have been given shape, the next frontier involves ergonomics, observability, and scaling out.
Future Combinators & Parallel Map (pmap)
I have Nursery for structured concurrency, Channel->select for CSP races, and Stream for FRP. But for everyday Futures/Promises, you probably want to wait on multiple discrete outcomes easily. Here are my proposals to myself:
Future->wait_all(@futures)/wait_any: Similar to JavaScript'sPromise.all()/Promise.race().pmap: A high-level helper to map an array over a bounded pool of fibers. E.g.,my @results = Parataxis->pmap({ concurrency => 5 }, \&download, @urls);...I'll work on the syntax. And you can build this brick by brick using aWaitGroup+Channelbut providing it as a one-liner is a huge UX win.
Deterministic Testing / "Mock Time"
Modern async frameworks (like Rust's tokio or Python's asyncio test suites) feature a "mock time" scheduler.
- If a user writes
await_sleep(60_000)orTicker->new(interval => 3600_000), unit testing that is currently impossible without wall-clock waiting. - Feature: A mode where the scheduler's event loop artificially advances virtual time when all fibers are parked on timers, allowing a 1-hour timeout to be tested in 2 milliseconds.
Execution Contexts / CPU-bound Pools
Currently, blocking C-level jobs (like await_sleep and socket I/O) are offloaded to the C thread pool. But what if a Perl fiber wants to do heavy, CPU-bound Perl math (like image processing or massive JSON parsing) without stalling other cooperative fibers?
A potential solution is a spawn_blocking or yield_to_thread mechanism, similar to Java's Loom or Node's Worker_Threads, where a heavy task can be seamlessly shipped to a dedicated background Perl interpreter thread if thread-enabled Perl is used (rare these days due to speed), returning the result via a Future.
Observability and Trace Propagation
I have Acme::Parataxis::Local, which is great for fiber-local state.
In modern distributed tracing (OpenTelemetry, DataDog), trace IDs need to automatically inherit from the parent fiber when a child fiber is spawned. We can do this by adding a hook to spawn() that automatically copies or inherits specific Local keys from the parent to the child would allow seamless context propagation.
Graceful Shutdown & Application Lifecycle
You have stop() on Supervisor, and cleanup() at the C level. But robust daemon processes often need a coordinated shutdown sequence:
- A global
CancellationTokenthat fires when the process receivesSIGINT/SIGTERM. - Tying the top-level
run { ... }block to this token so that when the user pressesCtrl+C, the top-level nursery catches it, cancels all child fibers gracefully, allowsDESTROYblocks to finish, and exits cleanly, rather than the process dying instantly.
Comments