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.