parking

A minimal Rust crate for thread parking and unparking that avoids the shared-token deadlock risk of std::thread::park.

Library
Cargo
v2.2.1
81stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
35/100Needs Attention
Development Activity0
Maintenance20
Community40
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
83/100Excellent
Architecture78
Code Quality82
Innovation80
Learning Curve90

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/Unparker pair created via Parker::new() or the parking::pair() convenience function
  • Blocking park(), plus bounded park_timeout() and park_deadline() variants that return whether a notification arrived in time
  • A cloneable Unparker that can be shared across threads and safely called multiple times
  • will_unpark() and same_parker() helpers to check handle identity before doing unnecessary work
  • A zero-cost From<Unparker> for Waker conversion for wiring an Unparker directly into async task wakeups
  • An optional loom feature 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/Waker bridge 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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search