> Cinder 0.1.0 documentation: ADR-0008: Job system scheduling in Mojo, threads and parking from the shim
> Canonical page: https://cinder.ash10.dev/docs/adr/0008-job-system-threads/ · Source: `docs/adr/0008-job-system-threads.md` in the engine repository (engine commit 47c9352, imported 2026-11-09)
> Index of every page for agents: https://cinder.ash10.dev/llms.txt

# 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`, an `Atomic[Int32]`), and `wait` that helps execute jobs instead of blocking.
  All synchronisation uses `std.atomic.Atomic` with explicit `Ordering` arguments.
- **Threads and parking are C** (`native/src/threads.c`): `cn_thread_create/join/yield`,
  `cn_cpu_count`, and a counting semaphore (`sem_t` on Linux, `dispatch_semaphore_t` on macOS).
  Each worker thread's entry point is the Mojo function `_worker_entry` declared `abi("C")`, given
  a pointer to a heap-allocated `WorkerContext` that outlives the thread.
- **No allocation in steady state**: the job table, deques and worker contexts are allocated at
  construction. `submit` reports saturation with `False` and the caller runs the job inline;
  `reset` recycles 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 (`SEQUENTIAL` on the deque's `pop`/`steal` races, 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.
