Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
v0.1.1 - 2026-10-25
Borrowing patterns from Rust, Erlang, Java, etc. to bring more familiar concurrency primitives to Parataxis. No real changes to the core this time.
Added
Acme::Parataxis::Blocking: CPU offloading onto a bounded pool of cloned interpreters viaspawn_blocking(lazily loads threads/shared modules on first use).spawn_blocking_fork: Process-based CPU offloading for non-ithreads Perls with IPC viaStorable, bounded worker pools, and signal-based cancellation viakill_blocking_fork.Acme::Parataxis::Actor: Mailbox-driven actors with bounded queues, backpressure,ask(returning aFuture),send, and graceful teardown.Acme::Parataxis::Supervisor: OTP-style supervision trees supportingOneForOne,OneForAll, andRestForOnerestart strategies with configurable restart budgets.- Synchronization primitives:
Acme::Parataxis::Sync::Mutex: Non-reentrant lock with strict FIFO hand-off and ownership tracking.Acme::Parataxis::Sync::RwLock: Writer-preferring, reentrant read/write lock.Acme::Parataxis::Sync::WaitGroup: Fiber job counter (add,done,wait).Acme::Parataxis::Sync::Barrier: Reusable phase rendezvous point.Acme::Parataxis::Sync::Once: Race-safe, single-execution initializer.
- Structured concurrency & flow control:
nursery( sub ($n) { ... } ): Structured fiber scopes where child errors cancel siblings and propagate outward.pmap( { concurrency => $n, }, sub ( $item ) { ... }, @items ): Parallel list mapping over a bounded fiber pool.with_timeout( $ms, [ $token, ] $code )andwith_cancel( $code ): Cooperative deadline and cancellation scopes.Acme::Parataxis::CancellationToken: Cooperative park-interrupt token.Acme::Parataxis::Local: Fiber-local ambient storage.Acme::Parataxis::Generator: Stackful, lazy pull iterator.Acme::Parataxis::Monitor: Non-owning fiber observer.
- Channels & Streams:
Acme::Parataxis::Channel::select: CSP-style waiting across multiple channels with random case selection to prevent starvation.try_get/try_putnon-blocking channel operations and per-channel timeout configurations.Acme::Parataxis::Stream: Backpressure-driven channel pipelines (map,filter,batch,batch_time,throttle).Acme::Parataxis::Ticker: Drift-free interval timer.Acme::Parataxis::RateLimiter: Token bucket limiter driven byTicker.
- Software Transactional Memory (STM):
Acme::Parataxis::TVarcells andatomicallytransactions with conflict detection andretry(). - Future combinators:
wait_allandwait_any. - Event loop & OS integration:
- Event loop drivers:
Acme::Parataxis::Driver::MojoandAcme::Parataxis::Driver::IOAsync. - Transparent unblocking (
Acme::Parataxis::Compat): Opt-in cooperativeCORE::GLOBALoverrides forsleep,read, andsysread. - Deterministic virtual clock via
run( virtual => 1 )andadvance( $ms ). - Graceful shutdown handlers via
run( on_shutdown => ... )forSIGINT/SIGTERM. fd_setsize()introspection helper.
- Event loop drivers:
- Diagnostics & Introspection:
dump_fibers()and backtrace-rich deadlock diagnostics.- New exceptions:
Acme::Parataxis::Error,Error::Cancelled, andError::Timeout. - Verified Perl
defercompatibility within fiber scopes.
Fixed
- Event loop drivers now key watches by handle identity rather than recycled file descriptor numbers, eliminating lost wakeups and orphaned slots.
- Fixed non-numeric string values being silently accepted in
Supervisor->new( within => ... ). - Fiber jump environments (
PL_top_env) are now preserved across context switches, preventing crashes (0xC0000005) when completion callbacks throw. - Blessed exceptions stringifying to empty strings (e.g.
Error::Timeout) are no longer misidentified as clean exits. - Fixed bare
Died at ...rethrows inrun()andChannel->getwhen evaluated blocks or messages return falsey values (0,''). - Deadlock detection now resets scheduler run queues and state so subsequent runs remain usable.
- Awaited fiber crashes are now caught and rethrown by
awaitrather than killing the entire scheduler loop. await_sleepnow uses native condition variable timed waits (pthread_cond_timedwait,SleepConditionVariableCS) instead of 4ms polling.- Context switches now strictly maintain canonical
AvREIFY_onlystate on argument pads, fixingPerl_pp_entersubassertions onDEBUGGINGbuilds. - Fiber-parking primitives now clean up
@_landing pads before parking to avoid cross-fiber pad pollution. submit_c_jobnow broadcasts condition variables to prevent queued jobs from stalling behind unrelated worker sleeps.- Enforced platform
FD_SETSIZEbounds checks on worker pool read/write jobs and event-loop watches to prevent stack buffer overflows. - Prevented uninitialized memory bugs in BSD
get_cpu_countsysctl lookups. exit()from inside yielded fibers is now safely trapped and re-raised across all platforms.- Fixed pad argument and buffer truncation when multiple fibers park inside the same shared subroutine.
- Resolved
Can't undef active subroutinewhen disablingAcme::Parataxis::Compatby correctly cleaning up fabricatedCvDEPTHframes. - Corrected C function signature for
para_release_cv_depthunder Affix on Windows. - Fixed
Can't undef active subroutine during global destructionwhen multiple fibers park concurrently in the same CV (Pass 1c depth settlement). - Traversed nested runlevels (
si_prev/si_next) during depth settlement to preventcx_popsub_argsassertion failures inside test subtest blocks.
Changed
- Maximum fiber limits are now dynamic: configurable via
max_fibers()andset_max_fibers(). - Increased default fiber limit from 1,024 to 65,536.
v0.1.0 - 2026-09-21
This started as a silly little diversion in February but I'm using this in actual projects now. I've even used it to shake out bugs in Affix.
I might move it out of the Acme namespace...
Anyway, the major win is that fiber hot path has been moved from Perl into C and roughly tripled context swapping throughput with no change to the public API.
Added
Acme::Parataxis::Channel: a buffered FIFO message queue for producer/consumer patterns between fibers. Writers block when full, readers when empty; a capacity of1makes it a rendezvous point. Built on two semaphores.Acme::Parataxis::Future: a one-shot placeholder for an eventual computation result. A producer firesset_result/set_errorexactly once; consumers pick it up withawait/resultor register anon_readycallback.Acme::Parataxis::Semaphore: a counting semaphore with no ownership: blocked fibers are parked (no busy-wait) and resumed FIFO as permits become available.Acme::Parataxis::Signal: a two-state flag with a FIFO queue of waiters.sendlatches the signal so a laterwaitconsumes it immediately, whilebroadcastwakes every queued waiter at once (and drops if nobody is waiting).
Fixed
- Fixed crash (double-free / use-after-free) when fibers call Affix'd functions on non-threaded Perl. The bug was in Affix's
SAVEVPTR/SAVEDESTRUCTOR_Xarena pattern, which was not fiber-safe; now fixed upstream in Affix v1.2.5+. - Fixed SIGSEGV on macOS and FreeBSD caused by fiber stacks being only 512KB (via
posix_memalign). All POSIX platforms now use a 64MBmmap-backed stack with a PROT_NONE guard page, matching the Linux path. The SIGSEGV guard handler is also available on macOS/FreeBSD now. - Fixed FreeBSD compilation: added
MAP_ANONYMOUSw/MAP_ANONfallback. - Fixed macOS SIGBUS: the guard region size is now derived from
sysconf(_SC_PAGESIZE)at runtime so it always covers at least one full page (16 KiB on Apple Silicon). Also fixedcleanup()to usemunmap()instead offree()on non-Linux POSIX platforms. is_finished()now rejects fiber ids ofMAX_FIBERSor greater instead of reading out of bounds of the fiber table.- Closed a busy-spin footgun:
wait, fiberawait,Semaphorewaits, andSignal->waitnow croak instead of burning 100% CPU when called from outside the scheduler, and->newcroaks when the 1024-slot fiber table is exhausted rather than creating a fiber that can never run. - A fiber that yields during its initial run is now re-enqueued by the scheduler instead of being dropped, which previously could hang a regex-heavy workload.
- The scheduler no longer hangs when a fiber object is created but never spawned (
->newwithoutspawn): live-fiber tracking only counts fibers that have actually started, matching Coro's ready-queue semantics. async/runis now re-entrant: a nestedasyncinside anotherasyncor inside a fiber shares the one run loop (like Coro's single global scheduler) and returns the block's value, instead of clobbering the outer scheduler and deadlocking.- A destroyed fiber's id is kept out of the free list until every job it submitted has been reclaimed, so a stale completion can never be misdelivered to a (or corrupt) fiber that later reuses the id.
- Pending-job tracking now reads the C-side outstanding job count instead of a run-local counter, so jobs left over from a
stopped run are drained and handled by the next run instead of trippingFATAL: deadlock detectedor sitting in the done-queue forever. Semaphoreup/adjustskip stale (already destroyed) waiters instead of consuming a wake that should go to a live fiber.- Channel constructors now reject a capacity below 1 instead of deadlocking on it at load time.
- The 1024-slot job queue is no longer fatal on the first try:
_submit_jobyields once and retries before croaking. Future::set_result/set_errorwake awaiters exactly once instead of appending a duplicate_wake_waiterscallback on everyawait.
Changed
- Spawned fibers run inline at spawn time.
- The fiber registry is replaced by strong references to each fiber object in C.
- Fiber completion moved from a Perl method into C: the entry point writes state directly into the object's slots with
av_store, and only dispatches callbacks when callbacks were actually registered. - To save time on FFI boundary crossings,
spawnnow performs the whole create run sequence in a single call and builds the fiber object in C. - Fiber objects are incrementally-filled AVs instead of HVs.
- On x86_64 ELF, context switching uses a hand written trampoline that only saves the callee-saved registers and stack pointer, avoiding
swapcontext's signal-mask syscall. spawnandawaithot paths flattened by inlining helpers.- Worker threads block on
select()for the fullawait_read/await_writetimeout instead of polling every 10ms, cutting idle syscalls by ~50x. On POSIX a shutdown pipe wakes any worker blocked inselect()duringcleanup().
v0.0.10 - 2026-02-22
This version comes with a dynamic thread pool and an improved API.
Added
- New ergonomic API using exported functions like
async { ... },fiber { ... }, andawait( $target ).
Changed
- Refactored native thread pool to use cond vars (
PARA_COND_*) instead of busy polling, reducing idle CPU usage to near zero. - Switched to a global job queue for the thread pool for better load balancing across worker threads.
- Reduced default fiber stack size from 4MB to 512K.
- Worker threads are now only spawned when the first asynchronous job is submitted.
- Increased
MAX_FIBERSlimit to 1024. - Expose thread pool config with
set_max_threadsandmax_threads.
v0.0.9 - 2026-02-21
Asynchronous HTTP::Tiny is basically a semi-automatic footgun.
Fixed
- Resolved
AvFILLp(av) == -1and!AvREAL(av)assertion failures inPerl_pp_entersubonDEBUGGINGbuilds of Perl. This was fixed by ensuring Slot 0 (the argument array) of the next pad depth is correctly initialized during fiber context switches.
Changed
- Increased fiber stack size to 4MB to provide better support for deep Perl calls and regex operations. This is a temp solution.
v0.0.8 - 2026-02-19
All the remaining failing smokers all had old versions of Affix and sure enough when I installed v1.0.6, I saw the same failure. Always the most obvious thing...
Changed
- Require Affix v1.0.7
v0.0.7 - 2026-02-18
Another dist targetting a specific CPAN smoker. I cannot replicate the failure in https://www.cpantesters.org/cpan/report/f0ca1d14-0cfa-11f1-9988-e7d94c615303, so I'm just trying different things...
Fixed?
- Arguments passed to a fiber might not be released until the fiber object was destroyed.
v0.0.6 - 2026-02-18
Fixed
- Resolved assertion failures in
Perl_cx_popsub_argsandPerl_pp_entersubwhen running on aDEBUGGINGbuild of Perl. This was fixed by ensuringCvDEPTHand pads are correctly restored during context switches. (I hope...)
Added
- Added
--debugbuild to GitHub Actions matrix to ensure future compatibility with Perl debugging builds.
Changed
- Refactored
swap_perl_stateto be more robust regarding Perl's internal stack management.
v0.0.5 - 2026-02-18
Changed
- I'm honeslty just throwing stuff at the wall. Between my local machines and GH CI workflows, I cannot replicate some of the failures I'm seeing from smokers which makes them virtually impossible to resolve.
v0.0.4 - 2026-02-17
Changed
- Attempt to only spawn max X threads in
t/006_parallel.twhere X is 3 or theget_thread_pool_size()? See https://www.cpantesters.org/cpan/report/ecf1410e-0c46-11f1-8628-aee76d8775ea - Recalculate
PL_curpad = AvARRAY(PL_comppad)inswap_perl_state? See https://www.cpantesters.org/cpan/report/e7244bd8-0c44-11f1-b3ab-94362698fc84
v0.0.3 - 2026-02-17
Changed
- Adding an optional timeout to
await_readandawait_write. - Allow fibers to return complex data (AV*, HV*).
v0.0.2 - 2026-02-17
Fixed
- Fixed segfault in
coro_yieldby adding NULL checks for destroyed or missing fibers. - Resolved stall in exception handling by introducing
last_sendertracking to preventparent_idcycles.
Changed
- Made unit tests a lot more noisy
v0.0.1 - 2026-02-16
Changes
- It exists! It shouldn't but it does.