12 Oct 2026

feedPlanet Gentoo

A plea not to use uv-build

After seeing yet another dozen packages lose keywords on Gentoo because of a single key dependency switching to uv-build, I would like to plea on behalf of Gentoo users: please don't use uv-build as a build backend. Just to be clear, this is not about uv itself or any other Astral tools; these are good tools with a nice and responsive upstream. The uv-build backend is one thing we have to disagree on. I'm not saying it's bad; I'm saying that its benefits don't outweigh the drawbacks.

What are the drawbacks? It's really big and it's written in Rust. Rust has many advantages, but none of them really matter for a build backend that is used to build pure Python packages. It's a huge dependency (as of 0.13.0, its wheels are between 1.2 MiB and 1.5 MiB, depending on the platform) and it has no benefit over using a small pure Python backend (say, flit-core whose wheel is 45 KiB). It means that all Gentoo users need to build it from source, and given that it's released quite frequently (12 releases in the 30 days prior to writing this post), that's a lot of building. Like, for every 100 ms spent running uv-build to install one release of your package, Gentoo users will spend many hours building new uv-build versions over and over again.

However, build times aren't the biggest problem; portability is. You can run Python pretty much anywhere where you can get a working C compiler. Some minor features may not work, but the core is reasonably portable. The list of platforms supported by Rust is growing slowly, and even if you can get a working Rust compiler on a given platform, uv may not work. The problems can range from bugs in the lot of packages that uv depends on (for example, rustix::fs and its dependencies skipping SPARC, for no apparent reason), to the resulting executable being so big that the toolchain runs out of address space (and unlike system memory, you can't really add address space to a system; the Gentoo uv ebuild already carries two hacks to make it possible to build uv on 32-bit platforms). And then, if your platform is not supported at all… well, let's just say that adding and maintaining a new platform is a full-time job that may involve a lot of swearing at the code churn.

What about the advantages? Well, I'm aware of two reasons to use uv-build. One is consistency with using uv for your project, and it doesn't really matter. It's a backend and the whole point of PEP 517 is that you can use any backend with any tool. You can use flit-core, you can use hatchling, and either of them will work just fine with uv (or any other frontend). The other reason is performance and that argument makes even less sense. Even if it mattered, we're realistically talking about a difference in the ballpark of 100 ms, for an action executed rarely; it is equally possible that any gain will be outweighed by the time needed to fetch the 1.5 MiB wheel for uv-build. But it doesn't matter, because realistically the backend is only used by project developers, contributors, CI/CD workflows, downstream packagers and users of source distributions; all regular users use wheels instead. Of these groups, source distribution user group is the largest and the most impacted, and any performance gain is defeated by the time spent building uv-build.

12 Oct 2026 4:35pm GMT

07 Oct 2026

feedPlanet Gentoo

Gentoo infrastructure sponsors wanted!

Larry behind a server rack

Gentoo Linux is made possible by many community donations and sponsors, together with the enthusiastic work of our developers. Unfortunately, this year several long-running sponsors were unable to continue their prior support, and at the moment we are looking for replacements. Are you or your organization interested in supporting Gentoo via an infrastructure sponsorship? There are many ways to do that, ranging from providing rack space or servers in a data center to donating server hardware; details and typical arrangements are described on a wiki page of the Infrastructure project. Partnerships with organizations aligning with Gentoo's goals are strongly preferred. We can be reached at infra@gentoo.org. Please help us continue to provide the best source-based Linux distribution ever!

07 Oct 2026 5:00am GMT

02 Sep 2026

feedPlanet Gentoo

How hard is it to get a jobserver client right?

In November 2025, I've started working on steve, the jobserver for Gentoo. I wrote about it already, in the "One jobserver to rule them all" post. Back then, my main focus was on the motivation for a system-wide jobserver, and the technical details of getting it working. I have also mentioned a few client bugs we've discovered along the way.

Since then, we've found a few more bugs, as well as problematic design patterns. I think they're kind of interesting, so I've decided to dedicate this post specifically to them. As a disclaimer, my aim is not to pick on specific projects; I'm bringing them up as real examples of what we've hit, and how that impacts jobserver operation.

Missing token release on error

As I've mentioned in the previous post already, the POSIX jobserver implementation relies on clients fully accounting for tokens. Clients "acquire" (read) tokens when they need to start a job, and they "release" them (write them back) when the job's done; and they must eventually release all the tokens, or they're going to be lost and other clients won't be able to acquire them. The documentation is pretty explicit on that:

Your tool should be sure to write back the tokens it read, even under error conditions. This includes not only errors in your tool but also outside influences such as interrupts (SIGINT), etc. You may want to install signal handlers to manage this write-back.

POSIX jobserver (GNU make)

Is this hard? Well, it depends. For a start, there's a lot of signals to cover. There are the more obvious ones, like SIGINT and SIGTERM. There are the less obvious ones, like SIGSEGV and SIGILL; I mean, do you really need to catch them? I'd say a robust implementation has to. And then there is SIGKILL that you really can't catch.

Given that you can't catch SIGKILL, an indiscriminate OOM-kill could easily result in tokens being lost. Hence, the whole architecture of steve is built upon the idea that we can't rely on clients releasing tokens reliably and we need to work around that. Still, as a feature it can log whenever a client exits without releasing all the tokens. That way, I was quite surprised to learn that GNU make itself does not return jobserver tokens on SIGINT. Well, bugs happen.

But then, there are harder cases. For example, jobserver-rs can't install signal handlers, because it's a library. Any program using it needs to take care of that, and they do not necessarily do.

Missing error handling

Missing handling for signals is one thing. However, GCC did not implement error handling in jobserver code. It just assumes that the named pipe will open successfully, that reads will succeed and so on. If anything bad happens, GCC will crash with an ICE somewhere later on.

If this could be bad with a plain named pipe, having it backed by steve makes things even worse. Steve can literally crash (or be stopped by the user), and then suddenly all the clients connected to it can explode in a variety of ways. Admittedly, this is a corner case and I find it hard to argue what's the best behavior here; though I suppose, say, disabling jobserver support and proceeding with the implicit slot is still better than suddenly crashing.

Overzealous file type checks

The jobserver protocol was originally built using named pipes. However, to the best of my knowledge, it is impossible to build proper token accounting on top of that, and therefore avoid the risk of losing tokens. For this reason, steve is using a character device governed by CUSE. Some other implementations are using files governed by FUSE.

While GNU make itself never checked the underlying file type, many implementations do: we've removed the named pipe requirement in LLVM and in pytest-jobserver. This is not strictly a bug; rather an implementation choice that's impossible for us to satisfy.

Alexander Monakov proposed an interesting suggestion why that is the case: the original GNU make implementation checks the file descriptor type when using the older jobserver variation based on anonymous pipes. That implementation relies simultaneously on the child process inheriting the file descriptor, and the environment variable specifying its number. In a pathological case, the file descriptor could be closed and replaced by some other open file; in which case checking if it's a pipe is a last resort protection against starting to read or write a random file. However, when passing an explicit path rather than file descriptor, such a check is not really necessary.

Acquiring tokens in a child process

Traditionally, jobserver integration is part of the job scheduler. The scheduler acquires tokens, starts new jobs and releases them once finished. However, there is at least one case where that's not the case: pytest-jobserver plugin hooks into child processes rather than the scheduler.

Such an implementation is much easier, since it seamlessly integrates with pytest-xdist, requiring no changes to the scheduler. However, it also has a major limitation: you can't dynamically adjust the job count. The xdist plugin starts the specified number of jobs (which effectively becomes the upper bound), and the jobserver plugin throttles the actual test execution based on the availability of job tokens.

From steve's point of view, the limitation of this approach is that every job token is associated with a different process. Features such as per-process job limits or round-robin token delivery can't work reliably, because steve can't really associate tokens across processes (at least for the time being).

By the way, another side effect of this design was that originally pytest-jobserserver did not obtain job tokens for test collection.

Problems with implicit slot

Another problem I've mentioned already is implicit slots. Per the documentation:

Second, every command make starts has one implicit job slot reserved for it before it starts. Any tool which wants to participate in the jobserver protocol should assume it can always run one job without having to contact the jobserver at all.

Job Slots (GNU make)

This design makes sense. The job starting GNU make (or any other jobserver client) should be covered by a job slot already, either implicit or using a job token. While starting nested jobs, the client uses little CPU time, and it makes no sense for it to block another job slot. Besides, if not for that, every nested make invocation would consume one more job token, and you'd soon run out of tokens.

However, it is easy to miss this fine point and actually try to acquire a token for every job started. As a result, you end up running one job too few. On top of that, it's not always actually easy to implement that. For example, pytest-jobserver implementation ended up special-casing xdist job "gw0", which is imperfect and could lead to locking: "gw0" uses the implicit slot even if it has nothing else to do, and any other job will have to acquire another token to finish its tasks.

On the other hand, nasm-rs implemented the implicit slot by writing an extra job token to the pipe. I mean, for named pipe it is sound: by adding one more token to the pool, you account for the parent process not needing one, the children get as many tokens as they need, and the total job count matches. However, for steve it meant we actually had to allow processes to temporarily hold −1 token. Doable but kinda ugly.

Writing back the wrong token

There's another curious point in GNU make documentation:

It's important that when you release the job slot, you write back the same character you read. Don't assume that all tokens are the same character; different characters may have different meanings to GNU make.

POSIX jobserver (GNU make)

Can you guess which implementation did not comply?

Yes, of course GNU make did not write back the same character. GCC also does not preserve the token. Admittedly, I suppose steve is the first jobserver to actually use characters with different meanings. I've used that to distinguish different jobs, and therefore be able to tell how long a particular job is running. It's not a critical feature, it can be helpful when debugging though.

Holding on to tokens throughout multiple jobs

What would you debug, actually? Well, for example you could notice that whenever Cargo is building something large, everything else stops. For example, you start five more emerge processes, and not one of them is able to start. You SIGUSR1 steve and confirm that Cargo is holding all the tokens for a few minutes now.

So you double-check the code, and file an issue: Cargo does not return tokens immediately after finishing a single job. But is that a bug? Arguably, the specification doesn't prohibit that. Apparently it works out better when building Firefox. For building multiple packages in parallel, it is much worse, though.

What's to come?

Jobservers aren't anything new. However, the widespread interest in jobserver support seems to have appeared recently. If I were to hazard a guess, that'd be due to a combination of large projects becoming more heterogeneous, and toolchains becoming more complex. It's perfectly normal to be building a large project comprising of C, C++ and Rust sources, and possibly including some bundled libraries that add both ninja and make to the mix. On top of that, LTO can overlay its own concurrency. The jobserver protocol is the obvious solution to throttling, and it stood the test of time. It's supported by ninja, by Cargo, by GCC… and the list continues to be growing.

The idea of using a shared jobserver isn't new either. Jobserver integration was proposed for Gentoo a long time ago. NixOS experimented with jobservers as well. However, steve is pretty new and by design inevitably different from GNU make. We've found many issues. And as testing continues and even more client implementations emerge, we're bound to find more.

As a footnote, many different people have been repeatedly pointing out the limitations of the protocol, as well as announcing that they're working on something new and better. I don't hold my hopes high, and honestly, I don't want to have to bother with another big transition. The GNU make protocol may be ugly, but it's well supported and there are reasonably good ways to make it work. Steve has proven that.

02 Sep 2026 5:40pm GMT