Skip to content

sources: reproduce intermittent "unknown package" errors under concurrent execs (1/4) - #589

Open
jason-rl wants to merge 2 commits into
cashapp:masterfrom
jason-rl:jason/sync-race-01-reproducer
Open

sources: reproduce intermittent "unknown package" errors under concurrent execs (1/4)#589
jason-rl wants to merge 2 commits into
cashapp:masterfrom
jason-rl:jason/sync-race-01-reproducer

Conversation

@jason-rl

@jason-rl jason-rl commented Jul 27, 2026

Copy link
Copy Markdown

Summary

Tracked together with 3 related races in #593, which includes a reproduction of each (expected vs. actual) directly against master.

Running a Hermit-managed binary that hasn't been installed yet several times within a few milliseconds of each other can make some invocations fail with:

fatal:hermit: <name>: unknown package

even though the package is perfectly valid. GitSource.Sync (sources/git.go) has no cross-process or cross-goroutine locking, so concurrent syncs of the same not-yet-cloned manifest source race: every caller passes the same pre-sync check, clones independently, and each then wipes and replaces the shared manifest tree with os.RemoveAll + os.Rename -- an unlink storm that a concurrent reader can observe mid-way through as ENOENT.

This is 1 of 4 stacked PRs fixing this and three adjacent races found while investigating it -- see #593 for a reproduction of each. This PR adds only a regression test reproducing this race; it's expected to fail CI as-is, that's the point, it pins the bug down with a test before the fix (#590) lands. Please review it standalone; #590, #591, #592 build on top and are up for review too (each explains which commits are new to it). Both commits here are this PR's own (it's the base of the stack).

  • TestConcurrentSyncInProcess: N goroutines race GitSource.Sync against one shared source dir; a background reader fails the test if it ever sees a manifest it already found disappear.
  • TestConcurrentSyncAcrossProcesses: the same, but across genuine child processes (re-exec of the test binary) -- util/flock is deliberately re-entrant per-PID, so goroutines alone can't exercise real cross-process lock contention.
  • A follow-up commit adds a start barrier to both tests (every goroutine/child process blocks until all n have been spawned and signalled ready, then all are released together), so the race is exercised on every run instead of being at the mercy of scheduling/launch-order luck.

Test plan


This PR -- the investigation, code, and tests -- was drafted with AI assistance (Claude Code).

Executing a Hermit-managed binary that hasn't been installed yet,
several times within a few milliseconds of each other, can make some
invocations fail with "unknown package" even though the package is
perfectly valid. GitSource.Sync has no cross-process or cross-goroutine
locking, so concurrent syncs of the same not-yet-cloned source race:
each clones independently and then wipes and replaces the shared
manifest tree, leaving a window where a concurrent reader sees ENOENT
partway through.

TestConcurrentSyncInProcess and TestConcurrentSyncAcrossProcesses
reproduce this directly (the latter across genuine child processes,
since util/flock is deliberately re-entrant per-PID and so cannot
exercise cross-process contention from goroutines alone). Both fail
against the current implementation; the fix follows in a subsequent
change.
@jason-rl jason-rl changed the title sources: add reproducer for concurrent source sync race (1/4) sources: reproduce intermittent "unknown package" errors under concurrent execs (1/4) Jul 27, 2026
TestConcurrentSyncInProcess and TestConcurrentSyncAcrossProcesses
previously let goroutines/child processes begin racing as soon as each
was spawned, so on a fast machine some finished before the last one
even started, understating how often the race actually reproduces.
Hold every goroutine/child at a barrier until all have signalled ready,
then release them together, so all n consistently race through Sync
concurrently.

Also corrects TestConcurrentSyncInProcess's doc comment, which
overclaimed that -race specifically exercises "the process-local mutex
in sources/lock.go" -- that file doesn't exist yet at this point in the
stack.
@jason-rl
jason-rl marked this pull request as ready for review July 27, 2026 22:55
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.

1 participant