parking
A minimal Rust crate for thread parking and unparking that avoids the shared-token deadlock risk of std::thread::park.
Repository Health
Technical Analysis
parking gives Rust code a private, per-instance version of the standard library’s thread parking primitive. Calling Parker::new() produces a Parker and a cloneable Unparker handle: park() blocks the current thread until unpark() is called on the paired handle, then resets to the unnotified state. Because the notification state lives on the Parker/Unparker pair itself rather than being shared per-thread the way std::thread::park()/Thread::unpark() are, a function that parks internally can never accidentally consume a wakeup meant for its caller.
Beyond the basic park/unpark pair, the crate exposes park_timeout() and park_deadline() for bounded waits, will_unpark()/same_parker() for checking handle identity without side effects, and a From<Unparker> for Waker conversion so an Unparker can double as an async task waker without extra allocation. The crate forbids unsafe code, has zero required dependencies, and is one of the small synchronization building blocks maintained under the smol-rs umbrella alongside async-io and smol itself.
What You Get
- A
Parker/Unparkerpair created viaParker::new()or theparking::pair()convenience function - Blocking
park(), plus boundedpark_timeout()andpark_deadline()variants that return whether a notification arrived in time - A cloneable
Unparkerthat can be shared across threads and safely called multiple times will_unpark()andsame_parker()helpers to check handle identity before doing unnecessary work- A zero-cost
From<Unparker> for Wakerconversion for wiring anUnparkerdirectly into async task wakeups - An optional
loomfeature for model-checking the internal atomic state machine under exhaustive thread interleavings
Common Use Cases
- Building a blocking executor or reactor loop that needs a private wake channel instead of the process-wide std thread-park token
- Implementing a custom
Future/Wakerbridge where the waker needs to unblock a specific worker thread cheaply - Writing library code that parks internally without risking a deadlock if the caller also uses
std::thread::park()/unpark() - Coordinating a single producer/consumer handoff between two threads with a bounded wait and timeout fallback
Under The Hood
Architecture
The entire crate lives in one 449-line file (src/lib.rs): Parker wraps an Unparker, and Unparker wraps an Arc<Inner> holding an AtomicUsize state machine (EMPTY/PARKED/NOTIFIED), a Mutex<()>, and a Condvar. park() takes a fast lock-free path when already notified, otherwise locks the mutex, CAS’s into PARKED, and loops on cvar.wait/wait_timeout to absorb spurious wakeups by rechecking state. unpark() swaps state to NOTIFIED and, when a thread was PARKED, acquires and releases the lock before calling notify_one specifically to close a missed-wakeup race documented inline. It is a flat, single-module design with no layering or dependency injection — correctness rests entirely on getting that lock/notify ordering right, and reordering it would reintroduce the race the comments call out.
Tech Stack
Pure Rust, edition 2018, MSRV 1.51, with zero required dependencies. A loom 0.7 dependency is gated behind a cfg(loom)/loom feature combination used only for exhaustive concurrency model-checking, and easy-parallel 3.0.0 is a dev-dependency for spawning test threads. Everything else comes from std: sync::{atomic::AtomicUsize, Mutex, Condvar, Arc}, cell::Cell, task::{Wake, Waker}, and time::{Duration, Instant}. There are no build scripts and no unsafe code (#![forbid(unsafe_code)]). CI runs the test suite across nightly/beta/stable, a separate loom-instrumented job, and a pinned MSRV build.
Code Quality
Integration tests in tests/parking.rs cover park_timeout with unpark-before/unpark-never/unpark-from-another-thread, park_deadline, will_unpark/same_parker, and unpark’s return-value contract; tests/loom.rs reruns the core state machine under Loom for exhaustive interleaving checks. Every public item carries a doc comment with a compiling example. #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] plus CI-enforced clippy and -D warnings mean any lint violation fails the build, and logic invariants use explicit panics (e.g. “inconsistent park_timeout state”) rather than silently swallowing unexpected states.
API Design
The public surface is deliberately small: Parker::new/park/park_timeout/park_deadline/unpark/unparker, Unparker::unpark/will_unpark/same_parker/clone, a pair() convenience constructor, and a From<Unparker> for Waker conversion — getting started is one let (p, u) = parking::pair(); line with no configuration. Method names mirror std::thread::park()/unpark() closely enough that existing intuition transfers immediately, while the doc comments make the crate’s one real behavioral difference (private vs. process-shared state) explicit up front. There are no builder patterns, feature flags to learn for typical use, or generic parameters to reason about.