Skip to content

Commit c33d48b

Browse files
Fix mdformat violations in scheduler docs.
Apply mdformat to docs/explanation/scheduler.md so the Markdown Formatting CI check passes. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 42a7953 commit c33d48b

1 file changed

Lines changed: 32 additions & 32 deletions

File tree

docs/explanation/scheduler.md

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ For the user-facing view of pools, priorities, groups, and idle tasks, see [Thre
99
Every reaction execution is a **task** (`ReactionTask`) submitted to the scheduler. The `PowerPlant` owns a single `Scheduler` instance and forwards all work to it:
1010

1111
1. A trigger (message emit, timer, IO event, etc.) creates a `ReactionTask`.
12-
2. `PowerPlant::submit()` calls `Scheduler::submit()`.
13-
3. The scheduler resolves the target **pool**, acquires any required **group** tokens, and enqueues the task.
14-
4. A pool worker dequeues the task, runs the callback, and releases group locks when the callback returns.
12+
1. `PowerPlant::submit()` calls `Scheduler::submit()`.
13+
1. The scheduler resolves the target **pool**, acquires any required **group** tokens, and enqueues the task.
14+
1. A pool worker dequeues the task, runs the callback, and releases group locks when the callback returns.
1515

1616
`PowerPlant::start()` calls `Scheduler::start()`, which starts worker pools and then blocks the calling thread in the **MainThread** pool until shutdown. `PowerPlant::shutdown()` emits the shutdown event and calls `Scheduler::stop()`.
1717

@@ -129,11 +129,11 @@ If a reaction is bound with `Inline` and belongs to a single group, the schedule
129129

130130
Each pool holds an array of five `Queue<Task>` instances — one per priority bucket. At construction time the pool chooses the concrete queue type:
131131

132-
| Pool kind | Queue type | Why |
133-
| --------- | ---------- | --- |
134-
| Default pool (`Pool<>`) | `TaskQueue` (MPMC) | Concurrency may differ from the descriptor's nominal value; multiple workers dequeue concurrently. |
135-
| `MainThread`, Trace pool, any pool with `concurrency == 1` | `MPSCQueue` (MPSC) | Exactly one consumer; simpler and cheaper than MPMC. |
136-
| Custom pools with `concurrency > 1` | `TaskQueue` (MPMC) | Multiple workers compete for tasks. |
132+
| Pool kind | Queue type | Why |
133+
| ---------------------------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------- |
134+
| Default pool (`Pool<>`) | `TaskQueue` (MPMC) | Concurrency may differ from the descriptor's nominal value; multiple workers dequeue concurrently. |
135+
| `MainThread`, Trace pool, any pool with `concurrency == 1` | `MPSCQueue` (MPSC) | Exactly one consumer; simpler and cheaper than MPMC. |
136+
| Custom pools with `concurrency > 1` | `TaskQueue` (MPMC) | Multiple workers compete for tasks. |
137137

138138
The virtual `Queue` interface lets `Pool` store both implementations in one `std::array` without templating the entire pool. The virtual call cost is negligible compared to the atomic operations inside enqueue and dequeue.
139139

@@ -143,13 +143,13 @@ Workers identify themselves via a thread-local `Pool::current_pool` pointer, set
143143

144144
Tasks are not kept in one monolithic priority queue. Instead, each pool has **five fixed buckets** scanned from highest to lowest priority:
145145

146-
| Bucket | Priority range | DSL level |
147-
| ------ | -------------- | --------- |
148-
| REALTIME | ≥ 1000 | `Priority::REALTIME` |
149-
| HIGH | ≥ 750 | `Priority::HIGH` |
150-
| NORMAL | ≥ 500 | `Priority::NORMAL` (default) |
151-
| LOW | ≥ 250 | `Priority::LOW` |
152-
| IDLE | < 250 | `Priority::IDLE` |
146+
| Bucket | Priority range | DSL level |
147+
| -------- | -------------- | ---------------------------- |
148+
| REALTIME | ≥ 1000 | `Priority::REALTIME` |
149+
| HIGH | ≥ 750 | `Priority::HIGH` |
150+
| NORMAL | ≥ 500 | `Priority::NORMAL` (default) |
151+
| LOW | ≥ 250 | `Priority::LOW` |
152+
| IDLE | < 250 | `Priority::IDLE` |
153153

154154
`Pool::try_dequeue_task()` walks buckets 0→4 and returns the first available task. Within a bucket, ordering is **FIFO** (per-producer FIFO in the MPMC queue; strict FIFO in MPSC). Priority therefore dominates bucket order; tie-breaking within a bucket follows enqueue order, not reaction ID.
155155

@@ -198,8 +198,8 @@ The hot-path slot claim via `fetch_add` is wait-free within a non-full block. Se
198198
Most reactions belong to at most one group (including `Sync<T>`). For these, `Group::try_submit()`:
199199

200200
1. Tries to decrement `tokens` with a compare-exchange.
201-
2. On success, submits to the pool immediately with a `RunningLock` that calls `release_token()` on destruction.
202-
3. On failure, **parks** the task in priority-ordered waiter buckets via `park_publish()` / `park_reconcile()`.
201+
1. On success, submits to the pool immediately with a `RunningLock` that calls `release_token()` on destruction.
202+
1. On failure, **parks** the task in priority-ordered waiter buckets via `park_publish()` / `park_reconcile()`.
203203

204204
The token counter can go **negative** when waiters reserve slots they have not yet consumed. This signed counter, combined with per-waiter **arbiter slots** (`atomic<bool>`), ensures no lost wakeups and exact accounting when multiple waiters race with draining threads.
205205

@@ -230,36 +230,36 @@ Idle reactions (`on<Idle<>>`, `on<Idle<Pool<T>>>`) are registered via `PowerPlan
230230
When a pool worker finds no runnable task:
231231

232232
1. It tries `get_idle_task()` — acquiring counting locks that track per-thread and per-pool idle state.
233-
2. When all threads in a pool are idle and the pool holds the global idle lock, global idle reactions are collected.
234-
3. A synthetic `ReactionTask` runs that re-submits each idle reaction's task via `scheduler.submit()`.
233+
1. When all threads in a pool are idle and the pool holds the global idle lock, global idle reactions are collected.
234+
1. A synthetic `ReactionTask` runs that re-submits each idle reaction's task via `scheduler.submit()`.
235235

236236
`global_idle_count` is an atomic so pools can cheaply check whether global idle exists without locking the scheduler on every external-waiter registration.
237237

238238
### Shutdown sequence
239239

240240
`Scheduler::stop(force)` sets `running = false` and stops all pools.
241241

242-
| Stop type | Behaviour |
243-
| --------- | --------- |
244-
| `NORMAL` | Pools stop accepting new work (except **persistent** pools, which keep accepting during shutdown). Workers drain queued tasks. |
245-
| `FINAL` | Used after the main thread exits `start()`; even persistent pools stop once their queues empty. |
246-
| `FORCE` | Clears queues and wakes all threads; used for forced test timeouts. MPSC pools require the consumer thread to perform the drain. |
242+
| Stop type | Behaviour |
243+
| --------- | -------------------------------------------------------------------------------------------------------------------------------- |
244+
| `NORMAL` | Pools stop accepting new work (except **persistent** pools, which keep accepting during shutdown). Workers drain queued tasks. |
245+
| `FINAL` | Used after the main thread exits `start()`; even persistent pools stop once their queues empty. |
246+
| `FORCE` | Clears queues and wakes all threads; used for forced test timeouts. MPSC pools require the consumer thread to perform the drain. |
247247

248248
`Scheduler::start()` starts worker pools first, then blocks in `MainThread::start()`. When the main thread pool exits (after shutdown), pools are stopped in order — non-persistent pools before persistent ones — then joined.
249249

250250
Persistent pools (`ThreadPoolDescriptor::persistent`) continue accepting tasks during a normal shutdown so networking or logging reactors can finish in-flight work.
251251

252252
## Design tradeoffs
253253

254-
| Choice | Rationale |
255-
| ------ | --------- |
256-
| Virtual `Queue` interface | One bucket array in `Pool` without templating the entire pool; indirection cost is dwarfed by atomics. |
257-
| Separate `MPSCQueue` | Single-consumer pools avoid MPMC CAS on dequeue; meaningful win for `MainThread` and concurrency-1 pools. |
254+
| Choice | Rationale |
255+
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256+
| Virtual `Queue` interface | One bucket array in `Pool` without templating the entire pool; indirection cost is dwarfed by atomics. |
257+
| Separate `MPSCQueue` | Single-consumer pools avoid MPMC CAS on dequeue; meaningful win for `MainThread` and concurrency-1 pools. |
258258
| Priority buckets vs one sorted queue | Fixed five buckets give O(1) bucket selection and lock-free queues per level; fine-grained priority within a bucket is FIFO, not strict global ordering by task ID. |
259-
| Lock-free group fast path | Single-group `Sync` is the common case; parking in lock-free buckets avoids mutex contention on submission. |
260-
| Mutex for pool/group maps | Pools and groups are created once per descriptor; mutex cost is paid on first use, not every submit. |
261-
| Condition variable for workers | Lock-free queues hold tasks, but workers must sleep when idle; CV + `live` flag avoids busy-waiting. |
262-
| Non-preemptive execution | Simpler reasoning, no priority inversion from preemption; long tasks hold a thread until completion. |
259+
| Lock-free group fast path | Single-group `Sync` is the common case; parking in lock-free buckets avoids mutex contention on submission. |
260+
| Mutex for pool/group maps | Pools and groups are created once per descriptor; mutex cost is paid on first use, not every submit. |
261+
| Condition variable for workers | Lock-free queues hold tasks, but workers must sleep when idle; CV + `live` flag avoids busy-waiting. |
262+
| Non-preemptive execution | Simpler reasoning, no priority inversion from preemption; long tasks hold a thread until completion. |
263263

264264
## See also
265265

0 commit comments

Comments
 (0)