ADR-0008: Job system scheduling in Mojo, threads and parking from the shim
Status: Accepted, 2026-11-08
Context #
The brief requires a work-stealing job system written in Mojo that runs everything except SDL and
presentation. Mojo 1.2.0's standard library exposes parallelism_level() and parallelize() but
no public API to create OS threads or to block and wake a thread; its async runtime is documented as
private and unfinished (std.runtime). Spike 4 showed that Mojo abi("C") functions run correctly
when a native thread calls them.
Decision #
- Scheduling is Mojo (
cinder/core/jobs.mojo): a fixed-capacity job table, one Chase-Lev deque of job indices per thread (deque 0 is the main thread's), work stealing by idle workers, completion counters (Counter, anAtomic[Int32]), andwaitthat helps execute jobs instead of blocking. All synchronisation usesstd.atomic.Atomicwith explicitOrderingarguments. - Threads and parking are C (
native/src/threads.c):cn_thread_create/join/yield,cn_cpu_count, and a counting semaphore (sem_ton Linux,dispatch_semaphore_ton macOS). Each worker thread's entry point is the Mojo function_worker_entrydeclaredabi("C"), given a pointer to a heap-allocatedWorkerContextthat outlives the thread. - No allocation in steady state: the job table, deques and worker contexts are allocated at
construction.
submitreports saturation withFalseand the caller runs the job inline;resetrecycles the table at a point where no job is in flight (frame start).
Consequences #
- The threading model is documented in
docs/THREADING.md; the shim's thread code is covered by the ASan/UBSan CTest build and the Mojo tests run against both shim builds. - Memory orderings are conservative (
SEQUENTIALon the deque'spop/stealraces, acquire/release elsewhere). Relaxing them is a measured optimisation for Phase 8, not a correctness change. - Job functions are thin function pointers with an untyped data pointer; typed, closure-like submission helpers are layered on top in the ECS scheduler (Phase 5) where the job graph is known.