docs(build): correct OOM-daemon guidance and add global cargo/systemd knobs

systemd-oomd is desktop-only on Ubuntu (reverse-deps are ubuntu-desktop*);
'systemctl is-enabled systemd-oomd' returns not-found on this server, so
the previous advice to enable it would simply have failed. Recommend
earlyoom instead, with --avoid/--prefer tuned to protect the shell and
target rustc.

Also document the machine-wide alternatives to per-project tuning:
- sudo systemctl set-property user-1000.slice MemoryHigh=/MemoryMax=
  (covers every shell and build; sshd lives in system.slice so it can
  never lock you out)
- global ~/.cargo/config.toml, which does support [profile.*]

Clarify that the MemoryHigh warning applies to short-lived build scopes,
not to slice-level limits where gradual throttling is appropriate.
This commit is contained in:
2026-08-07 00:28:03 +00:00
parent a728c758a7
commit e2eb50eb85
+40 -12
View File
@@ -110,16 +110,43 @@ The machine-level protections below need no per-project changes.
```bash
# Reserve memory for the SSH daemon so it can't be swapped out entirely.
sudo systemctl edit ssh # [Service] / MemoryMin=128M
# Kill on memory *pressure* rather than waiting for true OOM. This reacts
# during thrashing — exactly the window where the box currently becomes
# unreachable. Ubuntu ships it but leaves it inactive.
sudo systemctl enable --now systemd-oomd
```
`earlyoom` is the lighter-weight alternative (`sudo apt install earlyoom`);
it kills only the single highest-scoring process, which during a build is
`rustc` itself.
**Do not bother with `systemd-oomd` on this box** — it is a separate
package whose only reverse-dependencies are `ubuntu-desktop*`, so Ubuntu
Server never installs it (`systemctl is-enabled systemd-oomd``not-found`
here). Use **earlyoom** instead, which is in `universe` and kills only the
single highest-scoring process rather than a whole cgroup:
```bash
sudo apt install earlyoom
# /etc/default/earlyoom
EARLYOOM_ARGS="-m 8 -s 5 -r 60 \
--avoid '(^|/)(systemd|sshd|mosh-server|tmux.*|bash|fish)$' \
--prefer '(^|/)(rustc|cargo|ld|lld|collect2)$'"
```
`--avoid` protects your shell; `--prefer` points it at the compiler. Fedora
enabled earlyoom by default for exactly this "system becomes completely
unresponsive, user has no choice but to force power off" scenario.
### Cap every build at once, forever (needs root once)
This is the global knob — no wrapper script, no per-project config:
```bash
sudo systemctl set-property user-1000.slice MemoryHigh=5G MemoryMax=6500M
```
Applies to every shell and every build you start, persists across reboots.
Because `sshd` itself lives in `system.slice`, capping the user slice can
never lock you out of a new login. (Ubuntu already ships `TasksMax=33%`
this way via `/usr/lib/systemd/system/user-.slice.d/`, so it's a
distro-blessed pattern.)
Note `MemoryHigh` at the *slice* level is reasonable — it throttles a
sprawling session gradually. Do not put it on a single short-lived build
scope, where it causes the reclaim-stall described below.
### Cap one build ad-hoc (no root)
@@ -136,10 +163,11 @@ OOM — it was *thrashing*. With swap available the kernel pages `sshd` out
to feed the build and the box goes catatonic while `oom_kill` stays at 0.
Denying the build swap turns a slow total failure into a fast contained one.
**Do not add `MemoryHigh`.** It sounds safer but traps the process in
continuous reclaim so it grinds forever instead of dying. Measured against
an identical 256 MB ceiling: `MemoryMax` alone → clean kill in seconds;
`MemoryMax` + `MemoryHigh` → still spinning after 45 seconds.
**Do not add `MemoryHigh` to a single build scope.** It sounds safer but
traps the process in continuous reclaim so it grinds forever instead of
dying. Measured against an identical 256 MB ceiling: `MemoryMax` alone →
clean kill in seconds; `MemoryMax` + `MemoryHigh` → still spinning after 45
seconds.
### Diagnosing which crate is expensive