[BUILTIN-PROCESS-FAILURE] (docs/specs/0012-Built-InFunctions.md) is a
platform-neutral contract: every one of the five spawn_process_with_handler
failure codes must leave the process exactly as the call found it — no
descriptor or handle held, no child unreaped, no table slot occupied, one handle
number spent. Both implementations claim it. Only one is tested.
Current state
POSIX is properly pinned. compiler/runtime/test_system_failures.c injects
each failure deterministically through the seams in
compiler/runtime/test_spawn_seam.h (static-link interposition of fork,
pthread_create, pthread_mutex_init and the allocator, with the real symbol
recovered via dlsym(RTLD_NEXT, ...)), and for every code asserts: the exact
return value, an unchanged live-allocation census, an unchanged lowest free
descriptor, the actual table slot released (measured with next_handle_number(),
not inferred from the error code), waitpid(-1, &st, WNOHANG) == -1 && errno == ECHILD, the exact next handle number, and a successful recovery spawn.
Win32 has none of that. The Windows branch of
system_runtime.c:392-677 had
several real acquisition-unwind defects; they are fixed in source — the two
CreatePipe calls are split so a failed second one does not strand the first,
-4/-5/-6 all unwind through an abandon_process that destroys the record
mutex and frees the command and record, SetHandleInformation failures are
checked and unwound, and the -5 arm now waits unconditionally after requesting
termination. Every one of those is source-only. Nothing executes them.
Why CI does not catch it
.github/workflows/ci-windows.yml runs
under msys2/MinGW and builds the runtime archives (make _runtime CC=gcc AR=ar)
plus exactly one C test (make _test_gc_stack_root). It never runs
make _test_c_runtime, so no row of the Makefile's C_TEST_SUITES table
executes on Windows — test_system_runtime included. The Windows unwind could
be deleted wholesale and every required check would stay green.
The POSIX seams cannot be reused: dlsym/RTLD_NEXT, fork and the pthread
interposers have no Win32 equivalent, so test_spawn_seam.h will not compile
there even under MinGW.
What is needed
- Win32 seams. Thin, injectable wrappers around
CreatePipe,
SetHandleInformation, CreateProcessA and the record allocation, each
armable to fail with a chosen GetLastError(). Because MinGW compiles the
_WIN32 branch normally, this can live beside the POSIX suite rather than in
a separate harness — the seam header is the only part that needs an #ifdef.
- A parity suite asserting, per failure code, the Win32 analogue of every
POSIX assertion above: exact return value, handle-count inventory across the
call (GetProcessHandleCount), the actual table slot released, confirmed
child termination (WaitForSingleObject returning WAIT_OBJECT_0 and an exit
code), the exact next handle number, and a successful recovery spawn.
- A CI step that runs it. Add
make _test_c_runtime (or a Windows-capable
subset) to ci-windows.yml as a job-level step. Per CLAUDE.md it must not
be gated with on: paths: — a path-filtered required check never reports and
blocks every merge forever.
Why this is filed rather than fixed
Writing Win32 test code that cannot be compiled or executed on the authoring
machine, and landing it straight into a required gate, is how a check that
asserts nothing gets added. The seams and the suite should be developed by
someone who can iterate against a real windows-latest runner, or against a
MinGW container, with the mutation check applied the same way the POSIX suite
had it: revert each unwind fix in turn and confirm the suite goes red.
Until then the branch ships POSIX-only evidence for a contract both
implementations claim, and the Windows half is unproven.
[BUILTIN-PROCESS-FAILURE](docs/specs/0012-Built-InFunctions.md) is aplatform-neutral contract: every one of the five
spawn_process_with_handlerfailure codes must leave the process exactly as the call found it — no
descriptor or handle held, no child unreaped, no table slot occupied, one handle
number spent. Both implementations claim it. Only one is tested.
Current state
POSIX is properly pinned.
compiler/runtime/test_system_failures.cinjectseach failure deterministically through the seams in
compiler/runtime/test_spawn_seam.h(static-link interposition offork,pthread_create,pthread_mutex_initand the allocator, with the real symbolrecovered via
dlsym(RTLD_NEXT, ...)), and for every code asserts: the exactreturn value, an unchanged live-allocation census, an unchanged lowest free
descriptor, the actual table slot released (measured with
next_handle_number(),not inferred from the error code),
waitpid(-1, &st, WNOHANG) == -1 && errno == ECHILD, the exact next handle number, and a successful recovery spawn.Win32 has none of that. The Windows branch of
system_runtime.c:392-677hadseveral real acquisition-unwind defects; they are fixed in source — the two
CreatePipecalls are split so a failed second one does not strand the first,-4/-5/-6all unwind through anabandon_processthat destroys the recordmutex and frees the command and record,
SetHandleInformationfailures arechecked and unwound, and the
-5arm now waits unconditionally after requestingtermination. Every one of those is source-only. Nothing executes them.
Why CI does not catch it
.github/workflows/ci-windows.ymlrunsunder msys2/MinGW and builds the runtime archives (
make _runtime CC=gcc AR=ar)plus exactly one C test (
make _test_gc_stack_root). It never runsmake _test_c_runtime, so no row of the Makefile'sC_TEST_SUITEStableexecutes on Windows —
test_system_runtimeincluded. The Windows unwind couldbe deleted wholesale and every required check would stay green.
The POSIX seams cannot be reused:
dlsym/RTLD_NEXT,forkand the pthreadinterposers have no Win32 equivalent, so
test_spawn_seam.hwill not compilethere even under MinGW.
What is needed
CreatePipe,SetHandleInformation,CreateProcessAand the record allocation, eacharmable to fail with a chosen
GetLastError(). Because MinGW compiles the_WIN32branch normally, this can live beside the POSIX suite rather than ina separate harness — the seam header is the only part that needs an
#ifdef.POSIX assertion above: exact return value, handle-count inventory across the
call (
GetProcessHandleCount), the actual table slot released, confirmedchild termination (
WaitForSingleObjectreturningWAIT_OBJECT_0and an exitcode), the exact next handle number, and a successful recovery spawn.
make _test_c_runtime(or a Windows-capablesubset) to
ci-windows.ymlas a job-level step. Per CLAUDE.md it must notbe gated with
on: paths:— a path-filtered required check never reports andblocks every merge forever.
Why this is filed rather than fixed
Writing Win32 test code that cannot be compiled or executed on the authoring
machine, and landing it straight into a required gate, is how a check that
asserts nothing gets added. The seams and the suite should be developed by
someone who can iterate against a real
windows-latestrunner, or against aMinGW container, with the mutation check applied the same way the POSIX suite
had it: revert each unwind fix in turn and confirm the suite goes red.
Until then the branch ships POSIX-only evidence for a contract both
implementations claim, and the Windows half is unproven.