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.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.