Skip to content

fix: rust-governor lock leaks to the sccache daemon and wedges every later build #69

Description

@kazmosahebi

The rust-governor shim's build lock can be captured permanently by the sccache daemon, after which every cargo invocation on the machine blocks forever with no timeout and no diagnostic.

Mechanism

~/.local/bin/cargo serialises builds on a per-user flock (shim lines 107-110):

exec 9>"$LOCK"
if ! flock -n 9; then
    notify_wait
    flock 9
fi

The comment above it (line 104) states: "The fd survives exec, so the lock lives exactly as long as the build and releases on any exit, crash included."

That holds only if no long-lived descendant inherits fd 9. A flock belongs to the open file description, so any daemon started during a governed build keeps the lock alive after the build itself exits.

sccache is exactly such a daemon. When hyperi-sccache.service is not running, cargo auto-spawns sccache inside the locked scope; it inherits fd 9, daemonises, and holds the lock until it is killed.

Trigger

hyperi-sccache.service ships disabled (with preset: enabled), so on a fresh SOE machine the first Rust build spawns sccache ad-hoc and the lock is captured immediately. The shim already anticipates the service (line 8: "sccache-hosted compiles are captured separately: hyperi-sccache.service carries Slice=rust-build.slice") - but nothing enables or starts it.

Symptom

A build that looks alive but does nothing:

  • 0% CPU, no rustc children
  • wchan is do_wait; its only child is flock 9
  • /proc/locks shows a holder PID that no longer exists

Observed while running a local quality gate over a Rust workspace:

$ stat -c '%i' /run/user/1001/hyperi-rust-govern-1001.lock
107
$ rg ":107 " /proc/locks
7: FLOCK  ADVISORY  WRITE 3078836 00:3f:107 0 EOF
7: -> FLOCK  ADVISORY  WRITE 3078951 00:3f:107 0 EOF
$ ps -p 3078836          # holder is dead
$ ls -l /proc/3078858/fd/9
... -> /run/user/1001/hyperi-rust-govern-1001.lock   # sccache holds it

Killing sccache releases the lock, but the respawned daemon re-inherits fd 9 within seconds, so the wedge returns on the next cold build.

Impact

hyperi-ci check --quick over the same Rust workspace, same machine, same commit:

elapsed avg CPU
lock captured 2670 s (44.5 min) 1%
after fix 32.75 s 95%

An 81x difference that presents as "Rust builds are slow" or "the CI hook is unusable", with nothing in any log to indicate a lock is involved. It affects every Rust build on the box, not just hyperi-ci. In this case it was misattributed to disk pressure for some time.

The macOS path does not have this bug

The non-flock branch (lines 113-130) stamps the holder's PID into the lock directory and clears it when kill -0 fails, so staleness is detected there. The Linux flock path has no equivalent, precisely because it assumes fd lifetime equals process lifetime.

Suggested fix

  1. Enable and start hyperi-sccache.service in the role. It is already written and already carries Slice=rust-build.slice; it just is not enabled. This removes the trigger.

  2. Bound the wait, as defence in depth. Any long-lived daemon spawned during a governed build reproduces this, so removing sccache as the trigger does not remove the class. flock -w <timeout> 9 degrades a captured lock into a bounded delay plus a warning naming the holder, instead of a permanent hang. A build that runs unserialised is better than one that never runs.

Workaround

pkill -x sccache
systemctl --user enable --now hyperi-sccache.service

Then confirm the service's fd 9 is a socket rather than the lock file, and that /proc/locks no longer lists the lock inode.

Not verified

  • Whether daemons other than sccache (rust-analyzer, a test harness leaving a background process) trigger the same capture. The mechanism says they would; only sccache was observed.
  • Whether the role intends the service to be enabled and this is a deployment regression, or whether it was always opt-in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions