Skip to content

test(hew-cli): serialize wasm stdlib archive builds under the shared bootstrap lock - #2874

Open
gertybotbot wants to merge 2 commits into
hew-lang:mainfrom
gertybotbot:wasi-stdlib-archive-lock
Open

test(hew-cli): serialize wasm stdlib archive builds under the shared bootstrap lock#2874
gertybotbot wants to merge 2 commits into
hew-lang:mainfrom
gertybotbot:wasi-stdlib-archive-lock

Conversation

@gertybotbot

Copy link
Copy Markdown
Contributor

Refs #1924.

The asymmetry

bootstrap_wasi_runner calls two builders in sequence:

build_wasi_runtime_serialized(&target_dir, build_profile)?;
for (package, archive) in WASI_STDLIB_ARCHIVES {
    build_wasi_stdlib_archive(&target_dir, build_profile, package, archive)?;
}

The first takes a cross-process fd_lock before invoking cargo, and its doc comment
already names the hazard precisely:

without it, multiple test binaries each see the artifact absent and each launch
cargo build concurrently; Cargo writes the staticlib non-atomically, so races
corrupt or transiently delete the file and wasm-ld then fails with
"cannot open libhew_runtime.a".

The second builds the same kind of wasm32-wasip1 staticlib, from the same loop, into
the same directory — and had the identical is_file() fast path with no lock at
all
. libhew_std.a was left exposed to exactly the race its sibling documents.
hew-testutil's ensure_hew_lib_built locks too, so the unlocked stdlib builder was
the only one of the three that didn't.

That matches #1924's signature: the failure surfaces in the support harness rather
than an assertion, appears on PRs that touch neither hew-cli nor wasm-eval, and is
far likelier on the slower macOS runner where the build window stays open longer.

The change

Hoist the lock into open_wasi_bootstrap_lock and take it in both builders, so every
wasm32-wasip1 staticlib build serializes on one lock file regardless of which artifact
a given process found missing. A per-builder lock would not do: the point is that
different builders contend with each other.

Both keep their unlocked fast path and their post-lock re-check, so the common
all-artifacts-present case (CI pre-build, or a sibling finished first) is unchanged.

Verification

Mutual exclusion is proven directly rather than assumed, with a control:

order observed
with shared lock ENTER runtime / EXIT runtime / ENTER stdlib-hew-std / EXIT stdlib-hew-std / ENTER stdlib-2 / EXIT stdlib-2
control, lock removed ENTER runtime / ENTER stdlib-hew-std / ENTER stdlib-2 / EXIT...

Three processes on the real fd_lock + real lock filename. Locked: perfectly nested,
no interleaving. Unlocked: all three inside the critical section at once — i.e. three
concurrent cargo build invocations racing the same non-atomic staticlib write.

  • cargo clippy -p hew-cli --tests → RC=0 (captured bare, not through a pipe)
  • cargo test -p hew-cli --test eval_e2e wasm16 passed, 0 failed, RC=0,
    including eval_wasm_file_runtime_failure_exits_with_child_exit_code, the test
    named in the issue.

What this does not claim

A flake fix cannot be proven by a green run — passing was always the common case. The
evidence here is structural: a documented race, a sibling that guards against it, an
unguarded third path, and a control showing the guard is what prevents concurrency.
If #1924 recurs after this, the remaining suspect is the warm-binary/settling issue
in the body's second hypothesis (cf. #1887), which this does not address.

Issue #1924 is left open deliberately — closing it needs runner evidence over time,
not a merge.

…bootstrap lock

build_wasi_runtime_serialized takes a cross-process fd_lock before invoking
cargo, with a doc comment explaining that parallel nextest processes otherwise
each see the staticlib absent, each launch a build, and race Cargo's
non-atomic staticlib write so wasm-ld reads a truncated or transiently-absent
archive.

build_wasi_stdlib_archive is called from the same bootstrap loop, builds the
same kind of wasm32-wasip1 staticlib, and had the identical is_file() fast
path with no lock at all. That leaves libhew_std.a exposed to exactly the race
the sibling function documents -- consistent with hew-lang#1924, where the failure
surfaces in the support harness rather than an assertion and is far more
likely on the slower macOS runner.

Hoist the lock into open_wasi_bootstrap_lock and take it in both builders, so
all wasm32-wasip1 staticlib builds serialize on one lock file regardless of
which artifact a given process found missing. Both keep their unlocked fast
path, so the common all-artifacts-present case is unchanged.

Refs hew-lang#1924
The CI 'Clippy & format' job covers rustfmt as well as clippy; the local
check ran only clippy, so a hand-written over-long format! passed locally
and failed CI. Reflow only, no logic change.
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