Skip to content

Fix lost-wakeup race on packageWaits in WaitForPackage - #3576

Merged
peterebden merged 1 commit into
thought-machine:masterfrom
jackmarsh:fix/waitforpackage-race
Aug 17, 2026
Merged

Fix lost-wakeup race on packageWaits in WaitForPackage#3576
peterebden merged 1 commit into
thought-machine:masterfrom
jackmarsh:fix/waitforpackage-race

Conversation

@jackmarsh

@jackmarsh jackmarsh commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

I've been building rust-rules, a Rust plugin for Please. While testing it on a repo with ~40 packages, each subincluding the plugin's build_defs, cold builds would intermittently hang at N tasks left, 0 workers busy, parsing M BUILD files.

The goroutine dump showed a single parser stuck in subincludeTarget → WaitForPackage → waitOnChan. The cause is a race in WaitForPackage. Two parsers racing for the same unparsed package can both see packageWaits.Get(key) == nil and both call Set. The second Set overwrites the first thread's channel, and LogParseResult closes only the channel currently in the map, so the first thread waits forever.

The fix makes the insertion atomic with AddOrGet, matching pendingPackages and pendingTargets in the same file. One caller inserts the canonical channel and queues the parse; everyone else waits on that channel and is woken by the existing close.

Added a regression test that races 32 callers against the package parse completing. It fails on master within a few iterations and passes with the fix, including under -race.

Reproduced on v17.27.0 and v17.31.2. Stock plz wedged 11 of 30 cold cycles on my machine; patched ran 30 clean. plz test //src/core/... passes.

Concurrent callers waiting on the same unparsed package could both pass
the Get(key) == nil check and both Set a fresh channel; the second Set
overwrote the first, and LogParseResult closes only the channel currently
in the map, so the first waiter slept forever and the build wedged with
"N tasks left, 0 workers busy". Use AddOrGet, as pendingPackages and
pendingTargets already do.
jackmarsh added a commit to becomeliminal/rust-rules that referenced this pull request Aug 16, 2026
Walks the workspace members and writes a BUILD file next to each:
rust_library and rust_binary for the crate's products, rust_test for unit
and integration tests, modules from the source tree, path dependencies
mapped to member labels and registry dependencies to third_party aliases.
Workspace-inherited edition and dependencies resolve through the root
manifest. On a bare cargo repo it also scaffolds third_party/rust/BUILD,
.plzconfig and plugins/BUILD, then chains the workspace's Cargo.lock into
the existing lockfile import. Existing BUILD files are never overwritten;
build scripts, optional features and renames are reported for follow-up.

Proven end to end: a 40-crate cargo workspace imports, builds and tests
green through the plugin. That repo also surfaced an upstream plz race
(WaitForPackage lost-wakeup, wedged parse at scale) — root-caused, fix +
regression test submitted as thought-machine/please#3576, noted in
PARITY hardening.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@peterebden peterebden left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, that sounds right; I think I found a similar thing in #3569 . Ended up changing Get completely, but this seems fine here.

@peterebden
peterebden merged commit 9ee7615 into thought-machine:master Aug 17, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants