[uv] OnceMap: Rust Pattern for Running Concurrent Work Exactly Once

3 min read Original article ↗

uv, the Rust-based Python package manager from Astral, is 10-100x faster than pip. As part of the speedup, uv aggressively parallelizes dependency resolution, wheel downloads, and source builds.

When running uv pip install scipy pandas, both packages depend on numpy, causing two threads to independently fetch numpy metadata. Without coordination, this triggers redundant requests. OnceMap ensures only the first caller fetches the data while others wait for the result.

Parallelization creates a coordination challenge. When multiple tasks need the same resource, how do you ensure the work happens exactly once? uv’s solution is OnceMap - a lightweight concurrent memoization primitive that powers deduplication across the resolver and installer.

astral-sh/uv:lib.rs#L10-L21

The OnceMap struct wraps DashMap, a concurrent hash map. Unlike a standard HashMap with a single lock, DashMap shards data into segments, each with its own lock, enabling parallel access without contention.

astral-sh/uv:lib.rs#L152-L156

Value enum is a crucial element. An entry tracks state beyond simple presence: in progress, completed, or not started. The Waiting variant holds a Tokio Notify, a lightweight synchronization primitive that lets tasks sleep and wake efficiently.

OnceMap uses a three-method protocol: register, done, and wait.

astral-sh/uv:lib.rs#L44-L58

register returns a boolean contract. If true, you must perform the work and call done. If false, another task is already handling it.

astral-sh/uv:lib.rs#L60-L65

done replaces the Waiting sentinel with the result and wakes all pending tasks using notify_waiters().

astral-sh/uv:lib.rs#L67-L95

This design addresses a race condition discovered during development (Issue #3724).

The original implementation had a flaw:

  1. Task A calls register() and starts work.

  2. Task B calls register(), sees it’s in progress, and calls wait().

  3. Task B retrieves the Notify handle.

  4. Task A finishes and calls done() (signaling waiters).

  5. Task B calls notify.notified().await

Notify signals are not queued, so Task B misses the notification and hangs forever.

The fix is to register as a waiter before checking the map again as implemented in wait() above.

pin!(notify.notified()) registers Task B to receive signals immediately. Even if Task A calls notify_waiters() during the second get(key) check, Task B will catch it. The pin! macro keeps the future at a fixed memory address. Rust futures are movable values by default. Since Notified registers its own address in Tokio’s wait queue, moving it afterward would leave a dangling pointer. pin! guarantees the future stays in place.

Note that the race condition fix is entirely in wait().

OnceMap coordinates wheel downloads and metadata fetching. Here is how the installer uses it.

astral-sh/uv:preparer.rs#L109-L211

One task performs the work while concurrent requests wait for the shared result.

  • Why not a standard cache? A cache stores results but doesn’t track in-flight work, so it can’t prevent duplicate requests.

  • Why DashMap + Notify? DashMap minimizes contention with fine-grained locking; Notify efficiently handles async synchronization.

  • Why clone on retrieval? Returning references requires holding locks across async boundaries, risking deadlocks. Arc<V> makes cloning efficient.

Discussion about this post

Ready for more?