-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Race condition in RPC filesystem cache. #7531
Conversation
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify site configuration. |
cfc61ef
to
2e5acdf
Compare
Thanks for tackling the issue! The idea sounds right, but maybe do you have any reference of this pattern? I feel |
Here's a similar existing implementation: https://github.com/npm/write-file-atomic/blob/main/lib/index.js. It's well used (53M last week or whatever), and solves the problems in the same basic way (i.e. write to random file, move across, unlink if error), but done in a way that is externally identical to writeFile (i.e. it supports FDs in addition to paths, etc). I'd assume you don't want to take on another dependency, but that would be a totally fine solution. I'm happy to make that change if that's desired. |
Thanks for the pointer and explanation 👍 Like you said, we don't likely want an extra dependency for this, but I was just wondering if there's any extended commentary of this approach since I'm not familiar with low level aspect of file system operation. |
I wasn't able to find a nice, packaged article on this (maybe I should write one?) - but the summary is basically that |
@vitest/browser
@vitest/coverage-istanbul
@vitest/coverage-v8
@vitest/expect
@vitest/mocker
@vitest/pretty-format
@vitest/runner
@vitest/snapshot
@vitest/spy
@vitest/ui
@vitest/utils
vite-node
vitest
@vitest/web-worker
@vitest/ws-client
commit: |
Now I'm digging into this and there might be a different issue. Here is a minimal simulation of the issue https://github.com/hi-ogawa/reproductions/blob/main/vitest-7531-atomic-write-file/repro.js The issue is that if (promises.has(tmp)) { // 👈
await promises.get(tmp);
return { id: tmp };
}
if (!created.has(dir)) {
await mkdir(dir, { recursive: true });
created.add(dir);
}
promises.set( // 👈
tmp,
writeFile(tmp, code).finally(() => promises.delete(tmp)),
); By reversing if (!created.has(dir)) {
await mkdir(dir, { recursive: true });
created.add(dir);
}
if (promises.has(tmp)) { // 👈
await promises.get(tmp);
return { id: tmp };
}
promises.set( // 👈
tmp,
writeFile(tmp, code).finally(() => promises.delete(tmp)),
); |
Ooh! That's a promising/problematic race condition as well. Unfortunately, it doesn't appear to fix the specific case I'm working on (it failed after 37m of reruns, which is right in line with the |
I think |
My confusion is still the persistent windows failure. I don't have a windows setup handy, how do you recommend debugging this? |
Oh, I thought Windows is just flaky on CI, but actually this change affects it somehow? I don't have Windows either and don't have a clue yet. Let me try a few CI rerun. |
Ah okay, this log says something https://github.com/vitest-dev/vitest/actions/runs/13467699629/job/37748891146?pr=7531#step:8:250 I'm not familiar with Windows fs, but I'd assume there's something special about it.
|
2e5acdf
to
0e3467b
Compare
I've made a windows-specific fallback for this case (where rename doesn't work because the target file already exists). I couldn't figure out a way to make the write atomic on windows. In principle, it would require sharing a lock between client & server for the cache. |
0e3467b
to
c15c956
Compare
An alternative solution would be to lock the files across the RPC bridge with something like proper-lockfile. |
With my diff (M2 MacBook Pro, 3 runs of
Main:
No real difference, it would appear. |
c15c956
to
6b9b821
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The race condition might still exist somewhere else like #7546, but I think enforcing atomic write file here looks good to me, considering there's no perf impact and it doesn't require much code.
I wasn't involved in the original perf improvement PR #5592, so I'll wait for another reviews from the team 🙏
@@ -146,3 +150,21 @@ function handleRollupError(e: unknown): never { | |||
} | |||
throw e | |||
} | |||
|
|||
async function atomicWriteFile(realFilePath: string, data: string): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a few comments why we do this, and a link to this PR
6b9b821
to
050ef1a
Compare
Performance wise it should be fine because Vitest won't hit this code many times since it's cached. Let's merge it to see how it affects the ecosystem. |
Description
This fixes #6976.
writeFile
does not atomically write the file, and when the file is read by the other thread, it can (if the timing is just wrong), read an empty file. In the most common case, the/@vite/env
module empty, and defines don't show up. In principle, however, I believe it can happen with any file.Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
pnpm-lock.yaml
unless you introduce a new test example.Tests
pnpm test:ci
.Documentation
pnpm run docs
command.Changesets
feat:
,fix:
,perf:
,docs:
, orchore:
.