2.7 KiB
2.7 KiB
Cycle 3: Thread Parking for Instant Worker Wakeup — Development History
Commit:
acacc1b· 4 files · 59 insertions, 10 deletions
Motivation
Before this change, idle workers used thread::sleep with a fixed timeout to wait for new work. This meant an idle worker wouldn't notice new messages until its sleep timer expired — up to 1ms of unnecessary latency on the idle-to-active transition. Under bursty workloads, this sleep-based backoff wastes both time and power.
Competitor Analysis
| Runtime | Idle Strategy | Wakeup Mechanism |
|---|---|---|
| Tokio | Parker state machine (notified/sleeping/empty) | unpark() via atomic CAS |
| Linux | NO_HZ adaptive ticks (stop tick when idle) | Interrupt on new work |
| Go | notewakeup / futex |
OS-level wake |
| BEAM | Scheduler sleep + signal | Thread signal |
| Swactor (before) | thread::sleep(1ms) |
Timer expiry only |
Tokio's parker uses a 3-state machine (notified → sleeping → empty) with atomic transitions. The key insight: unpark() is a no-op if the thread isn't parked, so callers pay zero cost on the hot path.
Implementation
- Replaced
thread::sleepwiththread::park_timeoutin worker run loop - Workers register
thread::current()viaOnceLock<Thread>on startup send_toandspawncallThread::unpark()on target worker after enqueuing work- Cross-worker sends from
WorkerContextalso unpark the target - Zero new dependencies — uses only
std::sync::OnceLock+std::thread::park_timeout
Key files modified: src/worker.rs, src/runtime.rs, src/delivery.rs
Design Decisions
OnceLock<Thread>for thread handle storage — set-once semantics match the worker lifecycle (one thread per worker, never changes). Simpler thanMutex<Option<Thread>>.park_timeoutinstead ofpark— timeout ensures workers periodically wake even without explicit unpark, preventing permanent sleep if an unpark is missed.- Unpark on
send_toandspawn— these are the two operations that create work for a worker. The cost is a single atomic store (no-op if thread is already running). - No condvar —
thread::park/unparkis simpler and avoids the spurious wakeup complexity of condition variables. Tokio's parker validates this approach.
Tests Added
1 new test (51 → 52 total):
mt_parked_worker_wakes_on_send— verifies that a parked worker processes a message immediately after send (not after timeout)
Result
- 52 tests pass
- All workspace crates compile
- Idle-to-active latency reduced from up to 1ms to near-zero
- No overhead on hot path —
unpark()is a no-op when thread isn't parked