HARSH PRATAP SINGH

how package managers use PubGrub for Dependency Resolution

There are all sorts of dependency resolution methods used by various package managers, but I am specifically writing on PubGrub as it's used in uv package manager which I used to contribute to get some insights on how pro peps write Rust. In short, PubGrub is popular as :

You can go through it's internals and better, listen to the awesome lightening talk by its creator at DartConf.

The thing that nudged me to write this is something about concurreny. The PubGrub algorithm is a tight synchronous loop with complex mutable state that doesn't fit naturally into async/await (so cant be async as its sequential).

uv being clever

What's clever is that uv completely decouples the synchronous solver from parallel I/O using a two-thread architecture with channels:

SOoo, PubGrub stays simple and synchronous, but the solver can queue up to 300 requests before blocking, and those requests are served concurrently. Te network I/O get fully concurrent, biggest bottleneck (metadata fetching) gets parallelized without touching the solver logic. See the PR.

Others?

The Dart's original implementation was obviously sequential. I dont know why Poetry and pip (Python) also is sync featching one package at a time. Swift does lazy fetching (doesn't fetch metadata until after resolution decides a version, but still sequential, users report slowness). Cargo (rust) is blocking during resolution.

yarn and npm (the javascript world) is an interesting piece over here as they use async/await throughout the entire solver code, whereas uv keeps PubGrub fully synchronous and only parallelizes I/O via a separate thread. JavaScript is single-threaded with async by default. There's no way to write blocking code that doesn't block the entire event loop. So they had to make everything async. It uses Single-threaded event loop + libuv thread pool (heavy lifting happens in libuv's C++ threads). await fetchPackage(pkg) returns a Promise immediately; event loop switches to other tasks. Event loop never blocks, promises resolve later, JavaScript keeps running other code. As it uses single heap, references are naturally shared so no cross-thread synchronization needed.