fix(js-runtime): stop deleting a concurrently-installed runtime#2248
Open
shulaoda wants to merge 1 commit into
Open
fix(js-runtime): stop deleting a concurrently-installed runtime#2248shulaoda wants to merge 1 commit into
shulaoda wants to merge 1 commit into
Conversation
✅ Deploy Preview for viteplus-preview canceled.
|
Contributor
Native binary sizes (
|
| Artifact | Format | Base | PR | Change |
|---|---|---|---|---|
vp (Linux x64) |
Binary | 10.31 MiB | 10.31 MiB | 0 B (0.00%) |
vp (Linux x64) |
gzip -9 | 4.42 MiB | 4.42 MiB | -348 B (-0.01%) |
| NAPI (Linux x64) | Binary | 33.07 MiB | 33.07 MiB | 0 B (0.00%) |
| NAPI (Linux x64) | gzip -9 | 12.72 MiB | 12.72 MiB | 0 B (0.00%) |
vp (macOS ARM64) |
Binary | 7.64 MiB | 7.64 MiB | 0 B (0.00%) |
vp (macOS ARM64) |
gzip -9 | 3.84 MiB | 3.84 MiB | +175 B (+0.00%) |
| NAPI (macOS ARM64) | Binary | 40.50 MiB | 40.50 MiB | 0 B (0.00%) |
| NAPI (macOS ARM64) | gzip -9 | 16.97 MiB | 16.97 MiB | +5 B (+0.00%) |
vp (Windows x64) |
Binary | 8.35 MiB | 8.35 MiB | +1.00 KiB (+0.01%) |
vp (Windows x64) |
gzip -9 | 3.64 MiB | 3.64 MiB | -143 B (-0.00%) |
| NAPI (Windows x64) | Binary | 27.51 MiB | 27.51 MiB | 0 B (0.00%) |
| NAPI (Windows x64) | gzip -9 | 10.70 MiB | 10.70 MiB | +2 B (+0.00%) |
| Trampoline (Windows x64) | Binary | 203.00 KiB | 203.00 KiB | 0 B (0.00%) |
| Trampoline (Windows x64) | gzip -9 | 97.91 KiB | 97.91 KiB | -1 B (-0.00%) |
| Installer (Windows x64) | Binary | 4.44 MiB | 4.44 MiB | +512 B (+0.01%) |
| Installer (Windows x64) | gzip -9 | 2.08 MiB | 2.08 MiB | -108 B (-0.00%) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Installing a managed runtime (
download_runtime_with_providerinruntime.rs) runs four steps in order:install_dir/.../node) exists, return the cached runtimeinstall_direxists, treat it as an incomplete install andremove_dir_allitmove_to_cache: take the per-version lock, re-check, atomic rename intoinstall_dirOnly the rename in step 4 is protected by the lock. Step 2 runs without it, and it decides "incomplete" from the directory's existence, not from whether the binary is present.
The race, with two processes installing the same uncached version:
install_dirand the binary now exist (B's complete install)install_dirthat B just created, treats it as incomplete,remove_dir_alldeletes B's installBetween A's check in step 1 and A's cleanup in step 2, B finished. Because A's cleanup only tests the directory, it wipes B's complete install.
Impact: the cache self-heals because A reinstalls, but B already returned a
JsRuntimepointing atinstall_dir/.../node. During A's remove-then-reinstall window the binary is gone, so a concurrentexecfails withENOENT.Root cause: the cleanup ran outside the lock and keyed on the directory instead of the binary, so a complete install was misread as incomplete and deleted.
Fix
move_to_cachetakes a newbinary_pathargument. The completeness check and the cleanup move into it, under the same lock as the rename, and key off the binary:The caller's unlocked cleanup in step 2 is deleted.
Why the race is closed: the completeness check, the removal, and the rename now all happen under one lock and all look at the binary. Replaying the timeline, A reaches
move_to_cache, takes the lock after B, sees the binary, and skips, so it discards its own temp download instead of deleting B's install. Recovery of a stale partial install still works, since a directory with no binary is removed under the lock and replaced.Tests
Three deterministic
move_to_cachetests (filesystem only, no network) cover the three target states:move_to_cache_moves_into_absent_targetmove_to_cache_replaces_stale_incomplete_targetmove_to_cache_keeps_a_complete_target_intactThe cross-process race has a sub-millisecond window and cannot be reproduced deterministically in a unit test (the existing
test_concurrent_downloadsis#[ignore]d). These tests instead pin the invariant that makes the race safe:move_to_cachenever clobbers a complete install and always replaces a partial one. The actual deleter, the unlocked caller-side cleanup, is removed outright.Verification