Releases: gburd/libxtc
Release list
v1.20.1
libxtc (1.20.1-1) unstable; urgency=medium
- Build hygiene: libxtc now compiles with ZERO warnings on every build
target and platform, and CI enforces it (warnings are build
failures) so it stays that way. Fixed 7 library + 8 test warnings
under gcc/clang (-Wall -Wextra -Wpedantic) and 79 -> 0 under MSVC
/W3. The MSVC set included one real portability-layer type mismatch
(__os_call_once passing __os_once_t* where the Windows compat
pthread_once wants INIT_ONCE*); the rest were comment/macro/cast
hygiene (dropped <ntstatus.h> which re-defined ~65 SDK macros,
Winsock length casts, _Atomic-drop-on-free casts, an LLP64 sentinel
widen, a pipe-macro redefine). - Enforcement: the gcc + clang CI build now uses CFLAGS=-Werror, and
dist/build_msvc.bat adds /WX, so any new warning fails CI. - No public xtc_* API change and no behavior change from 1.20.0.
-- Greg Burd greg@burd.me Mon, 13 Jul 2026 06:48:58 -0400
v1.20.0
libxtc (1.20.0-1) unstable; urgency=medium
- Windows IOCP AFD socket poll: three real bugs fixed on an MSVC
Windows host. (1) The AFD poll IOCTL code was wrong
(CTL_CODE(0x12,9,...)=0x00120024 was rejected with
STATUS_INVALID_DEVICE_REQUEST); use the wepoll/libuv literal
0x00012024. (2) A stale-GetLastError check made an empty 0 ms poll
wrongly return XTC_E_INTERNAL and kill the loop; treat n_done==0 as
a timeout. (3) Synchronous AFD completions (accept-ready,
connect-complete) were never reaped; post a self-completion for the
sync case. Accept + connect readiness now flow. The async
data-ready completion remains open (the loopback echo stalls after
the client send) and is documented; the socket-echo smoke stays a
clean SKIP, and every other IOCP path (file AIO, cross-thread
wakeup, selective receive, xtc_xproc) passes on the host. - Windows slab FlsAlloc magazine validated on MSVC: a microbench shows
every alloc hits the fiber-local fast path (fast=2,000,000 slow=256,
26.3 M ops/s). The spawn-scale curve is unchanged by it -- the
spawn bottleneck past 4 loops is cross-loop work-steal cache thrash,
not allocation (an honest, measured finding). - No public xtc_* API change from 1.19.0.
-- Greg Burd greg@burd.me Sun, 12 Jul 2026 19:50:23 -0400
v1.19.0
libxtc (1.19.0-1) unstable; urgency=medium
- Layering: fixed the L2->L3 upward dependencies and added a
machine-enforced gate. The slab core is now a layer-neutral
allocator (only _os*); its one proc-dependent convenience
(xtc_slab_reaper_spawn) moved to src/ptc/slab_reaper.c, so the L2
event loop's use of the slab for the per-loop timer objects is no
longer an upward call. xtc_loop_fini's direct proc-table
unregister became a hook (__xtc_loop_fini_hook) the L3 proc layer
installs. New dist/s_layer lint (in make check + CI) forbids any
.c from including a strictly-higher layer's header, with one
documented XTC_LAYER_OK exception (io_net.c cooperative-wait). - Removed the L5 PostgreSQL adapter (milestone M16) from the plan:
libxtc is a general-purpose async runtime (L0-L4) that ships the
primitives, not a downstream adapter. PLAN.md and docs updated;
PostgreSQL survives only as illustrative motivation. - Windows scalability fast path: the slab per-thread magazine is now
backed by Fiber Local Storage (FlsAlloc) on Windows -- an FLS slot
correctly follows a fiber across SwitchToFiber, unlike the
__declspec(thread) static TLS that had to be disabled earlier.
Win32 FLS API usage MinGW-verified; full MSVC re-measurement awaits
a Windows host (EC2 benchmark parked; the Windows AFD socket-echo
remains host-blocked, documented). - CI: added a FreeBSD job (vmactions/freebsd-vm, full gmake check) and
a RISC-V 64 job (qemu-user cross-arch build + unit/property suites).
Both also re-verified by hand this round (FreeBSD 15, RISC-V 64
native). illumos-in-CI intentionally not added (no runner; nested
QEMU too slow per commit) -- stays periodic manual re-verify. - No public xtc_* API change from 1.18.0.
-- Greg Burd greg@burd.me Sun, 12 Jul 2026 15:50:33 -0400
v1.18.0
libxtc (1.18.0-1) unstable; urgency=medium
- Code-quality cleanup (no functional change; net -225 lines). From a
read-only audit against DRY/KISS/SRP/Clean-Code, three verified,
behavior-preserving refactors:- Extracted the fiber-stack pool and the S1 stack-reclaim lever
(previously duplicated near-identically between the fcontext and
ucontext coroutine substrates) into a new internal
src/inc/coro_common.h that both mmap-based substrates include.
Verified byte-faithful across default(uctx), forced-fctx, ASan
(detect_stack_use_after_return=1), and clang ThreadSanitizer. - Factored the repeated "cancel park timer + clear field" idiom
(10 inline copies across sync.c, lock_mgr.c, proc.c) into
__xtc_task_cancel_park_timer(). - Consistency: fsm.c/reg.c/xproc.c freed library-allocated recv
buffers with xtc_free; converted to __os_free to match the rest
of the library (xtc_free is the consumer alias, literally
{ __os_free(p); }, so this is pure consistency, not behavior).
Renamed sync.c's __rd_ready/__wr_ready to __can_grant_read/write.
- Extracted the fiber-stack pool and the S1 stack-reclaim lever
- No public xtc_* API change from 1.17.0.
-- Greg Burd greg@burd.me Sun, 12 Jul 2026 13:02:39 -0400
v1.17.0
libxtc (1.17.0-1) unstable; urgency=medium
- ThreadSanitizer support for the cooperative-fiber runtime (requested
by the PG-integration team). The coro substrates now emit the TSan
fiber-IDENTITY API (__tsan_create_fiber / __tsan_switch_to_fiber /
__tsan_destroy_fiber) under clang -fsanitize=thread, so TSan tracks
per-fiber happens-before across cooperative switches instead of
collapsing every coroutine into one confused thread. This is
DISTINCT from and mutually exclusive with the ASan stack-switch
annotations (v1.13.0): a TSan build emits only the identity calls,
an ASan build only the stack-switch calls, so a TSan build no longer
link-fails on __sanitizer_start_switch_fiber (which the TSan runtime
does not provide). clang-only by design (gcc libtsan has no fiber
support). A full clang TSan build runs the fiber suite
(test_fctx/async/proc/svr/chan) with zero TSan warnings; a new CI
job (tsan-fibers) keeps it green. Zero cost / zero symbols in a
non-sanitized build. - Test coverage: closed the documented io_common.c and svr.c
branch-coverage gaps with targeted guard tests (io_init/fini/wakeup
NULL guards, the live wakeup+coalesce path, and the xtc_svr
start/stop/join/reply/call/cast XTC_E_INVAL edges). - Docs: corrected the stale "CI not flipped to SUAR=1" caveat (flipped
since v1.13.0); recorded that Windows multi-core scalability is not
claimed and is gated behind the Win32-fiber-substrate fix. - No public xtc_* API change from 1.16.0.
-- Greg Burd greg@burd.me Sun, 12 Jul 2026 10:06:55 -0400
v1.16.0
libxtc (1.16.0-1) unstable; urgency=medium
- Windows xtc_xproc: fixed a cross-thread deadlock in the exit-latch
callback (it took the proc-table lock, an SRWLock, while the loop
thread held it inside xtc_monitor). The callback now only nudges
the loop via xtc_io_wakeup; the shadow proc observes the exit latch
on the loop thread. The control channel reads on a dedicated OS
thread. The end-to-end monitor is still gated behind a separate,
ASan-confirmed Win32-fiber-substrate memory-safety bug (documented);
POSIX xtc_xproc remains fully tested. - Portability layer (from the pg_threads.h review, ahead of the
Windows os_thread port): split the rwlock unlock into
__os_rwlock_rdunlock / _wrunlock (the Windows SRWLock has no
mode-agnostic release); static lock initializers XTC_OS_MUTEX_INIT /
XTC_OS_RWLOCK_INIT; __os_call_once / __os_once_t over pthread_once /
InitOnceExecuteOnce; and __os_thread_atexit for cleanup on threads
libxtc did not create (interop with host / carrier threads). New
m1 tests, man pages, and docs; no DST applies (OS threading
primitives are not sim-reachable). - Docs: correct the stale "CI not flipped to SUAR=1" caveat (flipped
since v1.13.0); record plainly that Windows multi-core scalability
is NOT claimed and is gated behind the Win32-fiber-substrate fix. - No change to the public xtc_* API from 1.15.0; the new _os*
primitives are the internal L0 portability surface.
-- Greg Burd greg@burd.me Sun, 12 Jul 2026 08:55:09 -0400
v1.15.0
libxtc (1.15.0-1) unstable; urgency=medium
- Windows xtc_xproc control channel now uses a dedicated reader thread.
Root-caused the v1.14.0 Windows end-to-end monitor hang: the child's
pump proc waited for control-socket frames via xtc_proc_wait_fd, but
the Windows IOCP loop cannot yet wait on an arbitrary socket's
readability (the AFD-poll path is unfinished), so the child never
received the parent's message and never exited. Fix: the Windows
child reads the control socket on a dedicated OS thread (blocking
recv) and forwards frames to the root proc via a cross-thread
xtc_send, sidestepping the socket-readiness gap -- the same approach
the BEAM uses for Windows port I/O. POSIX xtc_xproc is unchanged and
fully tested; the Windows reader-thread path compiles clean (MinGW +
MSVC in CI) and awaits an EC2 Windows box for end-to-end
re-validation, so the MSVC-smoke xproc check stays a documented SKIP
(KNOWN_ISSUES records it as implemented-pending-validation). - Benchmark note (not a code change): the reuse-mode loops=1->4
throughput dip observed on the EC2 192-core box is a placement
artifact, not a runtime bug -- libxtc deliberately does not pin loop
threads (the embedder pins), so at low core counts the OS floats the
reschedule-heavy threads across both NUMA sockets and work-stealing
thrashes cross-socket cache. Documented; numactl pinning is the
benchmark-side fix. - No public API change from 1.14.0; a Windows-only internal
reorganization of the xtc_xproc child runtime.
-- Greg Burd greg@burd.me Sat, 11 Jul 2026 21:43:35 -0400
v1.14.0
libxtc (1.14.0-1) unstable; urgency=medium
- Fiber-stack pool (both coro substrates): a per-thread, bounded pool
recycles freed fiber stacks so a spawn skips the mmap AND the
guard-page mprotect. Found on an EC2 192-core box: the exec-scale
spawn path was flat at ~7 M/s regardless of core count while the
scheduler / work-stealing / message paths scaled to 249 M/s, and
perf showed 56% of spawn time in mprotect -> the process-wide
mmap_lock. The pool removes that syscall + lock from every
steady-state spawn (perf-verified 56% -> 0.78%). Honest framing: it
is a syscall/lock elimination, not a throughput number on the
current benchmarks -- those are only mprotect-bound under a
pathological all-live-at-once burst the pool cannot help, and
realistic workloads already scale. Kept for the latency/scalability
win at near-zero cost. - bench_xproc_fanout: a cross-fork spawn/monitor scale probe (forks N
children, monitors each, collects every DOWN). 1000 real
forked+monitored children verified correct on the EC2 box. - First x86_64-Windows validation (EC2 Windows Server 2022, MSVC 2022
Build Tools; prior MSVC coverage was ARM64/santorini only): the
entire tree -- including the Windows xtc_xproc port, the stack-pool
coro substrates, and xtc_tail -- compiles + links with MSVC x86_64,
and every MSVC smoke check passes. The xtc_xproc CHILD path
(CreateProcess re-exec + sentinel + loopback control) works; the
end-to-end parent monitor round-trip is a documented WIP SKIP (POSIX
xtc_xproc is fully tested). Fixed a brittle argv sentinel scan in
xtc_xproc_win_child_maybe. - DST coverage: test_sim_pg (process-group broadcast fan-out to N
subscribers, deterministic + replay-identical), completing DST for
the recent orchestration features. The sim suite is now 52
deterministic tests. - Internal: EC2 scale-benchmark findings recorded (Intel Sapphire
Rapids vs Graviton4 -- the mprotect spawn ceiling is markedly worse
on Intel; the scheduler scales to 192 cores on both).
-- Greg Burd greg@burd.me Sat, 11 Jul 2026 19:18:50 -0400
v1.13.0
libxtc (1.13.0-1) unstable; urgency=medium
- Proc-teardown refcount: closes a use-after-free RACE CLASS at its
shared root. __resolve() handed out a struct xtc_proc * after
releasing the table lock, so a concurrent exit could free it
mid-delivery (a DOWN send, a cross-thread wake, a mailbox-stats
read). An atomic refcount taken while the table lock is held pins
the proc across every resolve->deliver; the struct is freed only
when the last reference drops. Validated by the CI ASan job, which
now runs with detect_stack_use_after_return=1 (previously blocked by
this race). New DST test test_sim_proc_teardown models the race
deterministically. - Sanitizer fiber-switch annotations: both coro substrates (fcontext
and ucontext) now call __sanitizer_start/finish_switch_fiber around
every stack switch, so ASan/TSan/LSan track libxtc's user-space
fiber switches instead of mis-attributing stack memory. The full
fiber runtime passes ASan with detect_stack_use_after_return=1 on
both substrates -- previously impossible. Zero cost / zero symbols
in a non-sanitized build. Requested by the threaded-PostgreSQL
integration. - Windows cross-process spawn/monitor (xtc_xproc): a real port, no
longer an XTC_E_NOSYS decline. A portable child-entry registry
(xtc_xproc_register_entry + xtc_xspawn_entry) addresses the child by
a registered NAME the identical binary resolves in parent and child,
bridging the absence of fork(); the Windows path re-execs via
CreateProcess, uses a hardened loopback-TCP control channel (Winsock
has no socketpair) with a per-spawn nonce, monitors exit via
RegisterWaitForSingleObject, and maps the unhandled-exception
NTSTATUS to a POSIX signal so a Windows crash surfaces as the same
SIGNAL-kind DOWN as POSIX. xtc_xproc_win_child_maybe() is wired into
the embedder's main(). - xtc_xlink: bidirectional cross-fork fate (xtc_link across the fork
boundary), rounding out the xtc_xproc relation set. - xtc_tail runtime microscope: (a) the binary dump format is now
compact AND portable -- a little-endian header + LEB128 delta-encoded
events (~9 bytes/event vs a 32-byte struct), byte-identical across
endianness and padding, matching dial9's space discipline; (b) the
MSG source records send / receive / mailbox-depth high-water
alongside the existing SCHED source; (c) a new offline viewer,
tools/xtc-tail.py, reads / filters / steps / summarizes a captured
trace (including --wake-latency to find lost/late wakeups), and the
gdb xtc-tail-dump command writes a live program's ring for it. - Docs: a consolidated, honest Windows-vs-POSIX model-gap section
(no fork, no socketpair, the int-fd Unix-ism, the DLL-export gap,
unlink-of-open-file) in the Windows matrix. - New public API: xtc_xproc_register_entry, xtc_xspawn_entry,
xtc_xlink, xtc_xproc_win_child_maybe; xtc_tail SEND/RECV/MBOX_HWM
event kinds; XTC_DOWN_KIND_NOCONNECTION (from 1.12.0). Sim suite
grows to 51 deterministic tests.
-- Greg Burd greg@burd.me Sat, 11 Jul 2026 16:27:48 -0400
v1.12.0
libxtc (1.12.0-1) unstable; urgency=medium
- Crash-aware registry (the R5 remainder). xtc_reg_reaper is a proc
the embedder spawns once with the registry as its argument; it
monitors every pid registered via xtc_reg_register_mon and
auto-drops it (xtc_reg_drop_pid) when that pid goes DOWN -- the
automatic form of the manual cleanup. xtc_svr_call_name adds
via-dispatch: address a gen_server by registered name instead of by
pid (the Erlang {via, ...} / global-name pattern). See xtc_reg(3). - Cross-fork spawn / send / monitor: xtc_xproc. Extends the
Erlang-style process relations across a fork() boundary -- a parent
xtc_xspawn()s a child running its own xtc runtime, then xtc_xsend()s
to it and xtc_xmonitor()s it as if it were a local process. A child
crash or exit surfaces as a normal, fully-classified xtc DOWN (exit
code / signal decoded from the child's waitpid status), or
XTC_DOWN_KIND_NOCONNECTION if the control channel dies first. The
single-host subset of the distributed design, over an xtc_osproc
control socketpair rather than TCP. POSIX only (Windows declines
with XTC_E_NOSYS). See xtc_xproc(3). - xtc_tail runtime microscope (phase 1). A dial9-inspired recorder of
individual runtime events tied to a precise instant -- the
event-level complement to xtc_stats (aggregates) and xtc_trace
(causal message trace). Phase 1 ships the SCHED source (proc spawn
/ exit), an in-process read visitor, and a versioned binary dump for
a future offline viewer. OFF by default and one branch when a
source is disabled, so it is safe to compile into production. The
MSG / IO / OS sources, on-disk spill, and viewer are staged
follow-ons. See xtc_tail(3). - New XTC_DOWN_KIND_NOCONNECTION down-kind (cross-process channel
death). New installed headers xtc_xproc.h, xtc_tail.h. New DST
coverage: test_sim_reg gains a dup-key membership + drop_pid
scenario (deterministic, replay-identical).
-- Greg Burd greg@burd.me Fri, 10 Jul 2026 18:57:22 -0400