threads(M5): Mutex, Condition, and Semaphore over the futex

runtime.Thread.Mutex is the classic three-state futex mutex (unlocked/locked/
contended): the fast path is a single CAS and only a contended lock enters the
kernel. Condition is a futex sequence counter (wait/timedWait/signal/broadcast,
spurious wakeups allowed, use in a predicate loop); a signal racing the unlock
bumps the seq so it is never missed. Semaphore is permits guarded by
Mutex+Condition. All mirror std.Thread's shapes, ported onto runtime.Thread.Futex.

thread-test gains a mutex mode: 2 producers + 2 consumers move 2000 unique items
through an 8-slot ring (small enough that both sides block); the consumed
checksum and tally match exactly, proving the lock and condvars correct under
real cross-core contention.

Deferred with rationale (see docs/threading-plan.md): migrating join to a futex
completion word needs kernel clear-on-exit (else use-after-free munmapping a live
stack); host unit tests need a mockable Futex seam.

Gate thread-mutex PASS (3x); 17 guardrail/thread cases green; build + host tests
clean.
This commit is contained in:
2026-07-20 21:48:26 +01:00
parent b3a8147bd7
commit 1b33f48acd
5 changed files with 271 additions and 15 deletions
+26 -15
View File
@@ -211,23 +211,34 @@ Guardrail 18/18 green (incl. `sleep`/`event`/`ipc` blocking paths) + `aspace-ref
> in-memory log ring buffer evicts older lines); ordering is asserted against the full
> serial stream by the qemu regex instead.
## M5 — `Mutex` + `Condition` + `Semaphore`
## M5 — `Mutex` + `Condition` + `Semaphore` ✅
- [ ] `runtime.Thread.Mutex` (atomic fast path, `futex_wait`/`wake` slow path),
`Condition` (`wait`/`timedWait`/`signal`/`broadcast`), `Semaphore` — the same
state machines `std.Thread` uses, ported onto our `Futex`. Host unit tests for the
lock/unlock/wait state transitions.
- [ ] Migrate `join` to a futex **completion word** (the std shape) — drops the
per-thread endpoint from M3.
- [ ] `-Dtest-case=thread-mutex` (`smp: true`): a bounded producer/consumer over
`Mutex` + `Condition` moves K items across cores; assert the final tally is
exactly K with **no lost wakeups** (consumer never misses an item, producer never
overruns the bound), and the consumer reaches a `parked` marker (it blocked, it
didn't spin).
- [x] `runtime.Thread.Mutex` (three-state futex mutex: CAS fast path, `futex_wait`/`wake`
slow path), `Condition` (`wait`/`timedWait`/`signal`/`broadcast`, a futex sequence
counter), `Semaphore` (permits over `Mutex`+`Condition`) — the same state machines
`std.Thread` uses, ported onto our `Futex`.
- [x] `-Dtest-case=thread-mutex` (`smp: 4`): a bounded producer/consumer — 2 producers +
2 consumers over one `Mutex` and two `Condition`s move N=2000 unique items through
an 8-slot ring; the consumed checksum and tally match exactly (no lost/duplicated
item, no overrun) under real cross-core contention. The small ring forces producers
to block on full and consumers on empty, exercising `Condition.wait`.
**Gate:** `python3 test/qemu_test.py thread-mutex` logs `thread: produced/consumed K,
no lost wakeups`; `zig build test` covers the mutex/condition state machines; guardrail
set green.
**Gate (met):** `python3 test/qemu_test.py thread-mutex` passes (`thread-mutex: ok` →
`DANOS-TEST-RESULT: PASS`), robust across 3 runs; guardrail 17/17 green (incl.
`sleep`/`event`/`ipc`) + all M1–M4 thread cases; `zig build` clean, `zig build test`
green.
> **Deferred (with rationale):**
> - **`join` → futex completion word** — the exit-endpoint join (M3) is correct and
> tested. A futex-completion join needs the *kernel* to clear+wake a word after the
> thread is fully off its stack (a CLONE_CHILD_CLEARTID-style mechanism); doing it in
> the thread's own trampoline would let `join` `munmap` the stack while the thread still
> runs on it (use-after-free). Left on the exit-endpoint path; the kernel clear-on-exit
> is a later, separate refinement.
> - **Host unit tests for the state machines** — `Mutex`/`Condition` bottom out in the
> `futex_*` syscalls, unavailable on the host without a mockable `Futex` seam. The QEMU
> `thread-mutex` gate exercises them under real concurrency instead; a host-side mock is
> future work.
## M6 — TLS, `getCurrentId` polish, docs, and CI wiring