Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Async, Concurrency, and Parallelism

uv uses Tokio for waiting, Rayon and worker pools for CPU/file work, and subprocesses for Python builds. The distinctions matter.

Limits follow resources

Concurrency has independent download, build, install, and cache-read limits. Shared Arc<Semaphore> values make the download/build budgets global across nested operations; a recursive build cannot accidentally create its own full budget.

Resolution overlaps evidence gathering

The resolver consumes requests from a bounded channel and uses buffer_unordered to let metadata futures progress independently. The comment at Resolver::fetch is important: fine-grained database/build limits provide pressure below this wide logical fan-out.

Preparation is concurrent but deduplicated

Preparer::prepare_stream builds a FuturesUnordered. Before doing work, each distribution calls InFlight::register_or_wait. One future becomes producer; same-key callers await and clone its terminal result. Concurrency therefore does not mean duplicate network and build work.

Blocking work crosses an explicit bridge

Archive parsing, Git operations, file locks, and synchronous build-backend work use spawn_blocking or dedicated workers. Python bytecode compilation uses a bounded channel and a fixed number of child-process workers. This protects Tokio workers and bounds processes, file descriptors, and queued paths.

Cancellation and mutation have different costs

Dropping a metadata request is often cheap. Interrupting an environment update can leave a multi-file partial transition, so uv orders planning, preparation, locking, and mutation to minimize that window. Atomic cache publication makes cancelled producers leave either a complete entry or no visible entry.