ReviewOS

home-lang/home

fix(async): get the work-stealing executor building and correct

#804
Closed chris opened this 9 days ago ·
9 days ago

Problem

packages/async is not in build.zig and cannot compile on current Zig (std.Thread.Mutex / std.Thread.Condition are gone). The executor also has real concurrency bugs: TaskQueue.init returns an error from a function that can't fail; many threads push into a global queue that is only safe for its owner; grow() frees the buffer while other workers may still read it; worker 0 runs on two threads at once; deinit never wakes parked workers and can hang. scheduler and events use the same removed APIs.

Evidence (origin/main @ d4b219507)

Scope

  • Wire async, scheduler, events into build.zig and zig build test.
  • Port them to the futex primitives.
  • Fix the deque (Chase-Lev with epoch/hazard reclamation or never-free growth), the global injector queue, worker-0 double scheduling and shutdown.

Acceptance criteria

  • zig build test covers async/scheduler/events.
  • A test running 1M tasks across all cores completes cleanly under TSan, repeated 100×.

Depends on: #802 Part of #814.

9 days ago

Pushed fd820e549 directly to main: fix(async): harden executor queue primitives (#804)

This first three-file slice fixes the executor primitives without weakening tests:

  • retires Michael–Scott queue heads until deinit, avoiding concurrent consumer UAF/ABA while still reclaiming every node
  • retires old Chase–Lev buffers until workers have joined, replacing the intentional grow leak with bounded reclamation
  • replaces removed std.Thread.Semaphore with Home’s futex implementation, including retained early notifications and monotonic relative timeouts
  • adds an 8-producer/8-consumer exact-once stress test (8,000 items)

Verification (serialized under the memory guard):

  • native: 20/20 tests pass
  • ThreadSanitizer: 20/20 tests pass; 188 MB peak tree footprint
  • Linux and Windows: all three files cross-compile; 49 MB peak
  • zig fmt --check: pass

#804 remains open. Next slices will fix runtime worker scheduling/startup/shutdown and wire the async package into the build before the 1M-task, 100-run acceptance test.

9 days ago

Pushed second direct-main slice faf992e39: fix(async): make executor lifecycle race-free (#804)

Correctness changes:

  • executes the task returned by findTask instead of discarding it and searching a second time
  • removes worker pointers to the temporary Runtime.init stack value; workers bind to the final runtime address at startup
  • gives each worker its own PRNG, removing concurrent access to one unsynchronized generator
  • uses worker TLS for owner-only local-queue pushes and round-robin unparking for external submissions
  • makes shutdown wake every futex parker and clears joined thread handles, preventing delayed shutdown/double join
  • adds typed future destructors and join-handle cleanup so task/future state is reclaimed
  • hardens partial-init and partial-thread-start error cleanup
  • replaces sleep-based runtime tests with completion-based synchronization; the multi-task test now requires all 10,000 tasks and their exact sum

Verification (serialized under the memory guard):

  • native runtime graph: 34/34 pass
  • ThreadSanitizer runtime graph: 34/34 pass
  • 10,000/10,000 scheduled tasks complete exactly
  • peak tree footprint: 268 MB
  • Linux and Windows cross-compilation: pass
  • zig fmt --check: pass

#804 remains open for build/package wiring, timer/event integration, and the final 1M-task × 100-run acceptance target.

9 days ago

Pushed third direct-main slice 3629910af: build(async): add executor to test graph (#804)

  • adds packages/async/src/async.zig as the public executor root
  • registers the async module and its futex dependency in build.zig
  • adds the async artifact to the umbrella test step (-Dfilter=async)
  • updates home.toml so package-local testing targets the work-stealing runtime rather than stale async_runtime.zig
  • rewrites the package README around the actual public API and test command

Verification:

  • direct package root: 35/35 pass
  • package-local manifest command: 35/35 pass
  • zig build test -Dfilter=async: pass; 169 MB peak tree footprint
  • Linux and Windows package-root compile checks: pass; 81 MB peak
  • Zig formatting + scoped Pickier: pass

#804 remains open for timer/event integration and final 1M-task × 100-run acceptance.

9 days ago

Direct-main progress: d45d496 (fix(concurrency): wire scheduler and events to futex locks (#804)). Scheduler and events are now part of the umbrella test graph; their removed std mutex usage is replaced by Home futex-backed StaticMutex. The scheduler now uses atomic stop state and atomic job IDs. Event emission now owns listener snapshots and removes once-listeners by stable IDs, preventing concurrent mutation from invalidating borrowed slices or indices. Verification: native scheduler 7/7, native events 5/5, TSan threading 45 pass + 3 platform skips + 0 failures, TSan scheduler 7/7, TSan events 5/5, umbrella scheduler/events filters pass, and Linux/Windows cross-compilation passes. This issue remains open: the final 1M-task all-core TSan run repeated 100x is not yet complete.

9 days ago

Direct-main progress: 70ffd08 (fix(async): bound executor memory under stress (#804)). The executor now stack-roots each poll waker and allocates only escaping clones, supports a separate bounded task allocator, and reclaims global-queue nodes at quiescent points instead of retaining them until runtime teardown. Added scripts/async-runtime-tsan.sh plus an exact-once/all-worker acceptance test using a bounded task pool and atomic bitset. Verification: native async 35/35; TSan async 35/35; umbrella async target passes; Linux and Windows cross-compilation pass; one full TSan run completed 1,000,000 tasks exactly once across all 10 workers with batch 4,096, peak guarded tree footprint 849 MB, exit 0. The harness defaults to 100 serial repetitions and compiles once. Acceptance status: 1/100 full repetitions complete, so this issue remains open.

9 days ago

Acceptance complete on origin/main. Commit 947d10e makes nested spawns wake peer workers and drives the soak through worker-local deques plus real stealing; 70ffd08 provides bounded task storage, stack-root wakers, quiescent queue reclamation, and the guarded compile-once TSan harness. Final run: 100/100 serial repetitions passed, each executing 1,000,000 tasks exactly once across all 10 workers (100,000,000 tasks total), with zero TSan findings. Test-process footprint stayed 50-56 MB, compile peak was 168 MB, and the host low-water remained above 40%. Supporting checks: native async 35/35, TSan async 35/35, umbrella async/scheduler/events targets pass, and Linux/Windows cross-compilation passes. Both acceptance criteria are satisfied.

Sign in to comment on this issue.