You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Lock-free scheduler with per-priority queues (#193)
## Summary
Rework `Scheduler` / `Pool` / `Group` to remove the global mutex
contention that showed up on the existing implementation under load:
- **Per-priority lock-free task queues.** Each `Pool` now owns five
independent MPMC `TaskQueue`s (one per priority bucket) instead of a
single mutex-protected priority queue. Submitting and dequeuing on
different priorities no longer contend at all, and same-priority submits
use a block-based lock-free FIFO with a graveyard-style block
reclamation scheme to avoid ABA.
- **MPSC specialisation for single-consumer pools.** Pools with
`concurrency == 1` (e.g. `MainThread`, `TraceController`) now construct
`MPSCQueue` instead of `TaskQueue`, giving a non-atomic consumer side
and removing the dequeue CAS entirely for those pools. Both queues sit
behind a `Queue<T>` polymorphic interface so `Pool` can hold an array of
buckets and dispatch the right type at construction.
- **Counting-semaphore Group fast path.** Single-`Sync` groups now
acquire via a signed `std::atomic<int> tokens` plus a lock-free
`TaskQueue` per priority for waiters, falling back to the mutex-backed
`GroupLock` only for multi-group submissions. Sync ordering inside a
group is strict; equal-priority tasks across pools see relaxed global
FIFO.
- **Idle bookkeeping off the pool mutex.** Pool idle tracking moved off
the main pool mutex so wake-ups no longer block submitters.
New lock-free primitives (`TaskQueue`, `MPSCQueue`, `Semaphore`) and the
Group counting fast path are covered by Catch2 BDD-style tests, plus a
scheduler microbenchmark (`tests/tests/Benchmark.cpp`, hidden behind the
`[.benchmark]` tag).
## TSAN race fixes (pre-existing)
While validating the changes under TSAN on macOS clang and Linux gcc 13
three pre-existing data races surfaced and are fixed in this PR:
- **`IOController_Posix.ipp` — `pollfd::events`.** The `IOFinished`
handler mutated `watches[].events` while only holding `tasks_mutex`, and
the poll thread read the same field from inside `::poll()` while only
holding `notifier.mutex`. `bump()` woke poll but released
`notifier.mutex` before the mutation, leaving the race window open. The
handler now writes the wake byte inline and holds `notifier.mutex`
across the `watches` update and the follow-up `fire_event` call (which
can also touch `watches[].events`).
- **`Scheduler::submit` — `Reaction::scheduler_data`.** The cached pool
was read/written from any submitting thread without synchronisation. It
is now a non-owning raw `Pool*` cached in a `std::atomic<void*>`
(release store / acquire load) rather than a `std::shared_ptr<void>`
accessed via `std::atomic_load`/`atomic_store` — the shared_ptr atomics
fall back to a small global mutex pool on libstdc++ and become a
contention point on hot submission paths. Pools outlive all reactions
(PowerPlant tears reactors down before the scheduler), so a raw pointer
is safe, and all racers resolve the same pool, so the benign store is
last-writer-wins.
- **`Watchdog` data store.** The service `time_point` was read by the
chrono controller while being mutated by user threads emitting a service
event, and the void specialisation returned a reference through a
temporary `shared_ptr` (latent dangling reference). Centralised
reads/writes through a per-`(WatchdogGroup, RuntimeType)` `std::mutex`,
made `get` return by value, and routed `WatchdogServicer::service`
through `WatchdogDataStore::service` so writes share the read mutex.
## Other
- `.gitignore` now matches `build-*/` so out-of-tree TSAN/ASAN/Release
build directories don't appear in `git status`.
- SonarCloud suppressions for the deliberate lock-free / placement-new
idioms live in `sonar-project.properties`.
## Test plan
- [x] macOS clang TSAN: `dsl/IO`, `dsl/Inline`, `dsl/Watchdog` 30/30
clean each; full suite ✓
- [x] macOS clang Release: full suite ✓
- [x] Linux gcc 13 TSAN (Docker): same three tests 30/30 clean; hot-path
tests under repeated runs with no TSAN warnings
- [x] Linux gcc 9 Release (Docker): full suite ✓
- [x] Lock-free primitives have BDD-style unit tests (including
queue-destructor leak coverage)
- [x] Scheduler microbenchmark exercises submit / dequeue / group
acquire under contention
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
0 commit comments