-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Core: Retry writeFile cache when EBUSY error occurs
#32981
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
Core: Retry writeFile cache when EBUSY error occurs
#32981
Conversation
|
I've marked this as a draft PR because I'm having problems with the tests. All five new tests pass, but I end up with an uncaught error at the end of the last test. I can't work out how that's happening, because the last test verifies that the promise is rejected. 😕 Hopefully someone can point me in the right direction. |
| for (let attempt = 1; attempt <= SAFE_WRITE_ATTEMPTS; attempt++) { | ||
| try { | ||
| await writeFile(fileName, fileData, fileOptions); | ||
| return; | ||
| } catch (err) { | ||
| // If an EBUSY error occurs on any attempt except | ||
| // the last, then wait for a bit and try again. | ||
| // https://github.com/storybookjs/storybook/issues/23131 | ||
| if ( | ||
| attempt < SAFE_WRITE_ATTEMPTS && | ||
| err instanceof Error && | ||
| 'code' in err && | ||
| err.code === 'EBUSY' | ||
| ) { | ||
| await new Promise((resolve) => setTimeout(resolve, 100)); | ||
| } else { | ||
| throw err; | ||
| } | ||
| } | ||
| } |
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 retry a write mechanism, seems something that belongs in a common helper function, not implemented in the file-cache mechanism directly.
Do you agree?
Then we can make the unit test purely test the retry-mechanism.
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.
That sounds OK. Did you have a preference for where to create the helper function? Is there an existing file that contains similar helper functions that it could be added to?
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.
I've moved it to code/core/src/common/utils/write-file-with-retry.ts, but feel free to move it elsewhere if there's somewhere better. 😄
|
View your CI Pipeline Execution ↗ for commit 283917b
☁️ Nx Cloud last updated this comment at |
This comment was marked as spam.
This comment was marked as spam.
Package BenchmarksCommit: No significant changes detected, all good. 👏 |
writeFile cache when EBUSY error occurs
This appears to be a bug in vitest: |
|
I think there's a bug in the unit test @reduckted |
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
code/core/src/common/utils/write-file-with-retry.test.ts (1)
7-14: Critical: Apply the previously suggested fix for mock state persistence.The past review comment correctly identified the root cause of the unhandled rejection you're seeing after test 5. The fix has not yet been applied:
- Line 7 is missing
{ spy: true }- Line 12-14
beforeEachis missing mock cleanupAs noted in the previous review, test 5 (line 84) uses
mockRejectedValue(not...Once), which makes the mock reject on ALL subsequent calls. Without proper mock cleanup, this state persists and causes the unhandled rejection.Apply the previously suggested fix:
-vi.mock('node:fs/promises'); +vi.mock('node:fs/promises', { spy: true });beforeEach(async () => { vi.useFakeTimers(); + vi.clearAllMocks(); });
🧹 Nitpick comments (1)
code/core/src/common/utils/write-file-with-retry.test.ts (1)
82-104: Consider using multiplemockRejectedValueOncecalls for safer test isolation.While the mock cleanup fix will resolve the unhandled rejection, this test could be made more robust by using five
mockRejectedValueOncecalls instead ofmockRejectedValue:- vi.mocked(fs.writeFile).mockRejectedValue(error); + vi.mocked(fs.writeFile) + .mockRejectedValueOnce(error) + .mockRejectedValueOnce(error) + .mockRejectedValueOnce(error) + .mockRejectedValueOnce(error) + .mockRejectedValueOnce(error);This approach mirrors the pattern in the test at lines 50-55 and ensures the mock behavior is scoped to exactly this test's 5 attempts, making the test more self-contained and less reliant on cleanup.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
code/core/src/common/utils/write-file-with-retry.test.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
code/core/src/common/utils/write-file-with-retry.test.ts (1)
code/core/src/common/utils/write-file-with-retry.ts (1)
writeFileWithRetry(7-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Core Unit Tests, windows-latest
- GitHub Check: normal
🔇 Additional comments (1)
code/core/src/common/utils/write-file-with-retry.test.ts (1)
20-104: Test coverage and logic are solid.The test suite comprehensively covers all code paths:
- Success on first attempt
- Non-retryable errors (missing code, non-EBUSY)
- EBUSY with eventual success after retries
- EBUSY persisting through all attempts
The fake timer assertions correctly validate the 100ms delay and 5-attempt limit. Once the mock cleanup issue is resolved, this test suite will properly validate the retry behavior without unhandled rejections.
|
Any chance of this making it into 10.1? It looks like the test failures are unrelated and happening in other PRs too. |
|
@reduckted QA for 10.1 is going on right now. I'll ping @yannbf and @valentinpalkovic here, to decide if it's something we want to add to the release. |
|
Thank you! |
|
I see the alpha versions of 10.2 have started being released. Any chance of this making it into 10.2? |
Closes #23131
What I did
When an
EBUSYerror occurs inFileSystemCache.set, wait for 100ms and try again. Try up to five times (including the original attempt). If theEBUSYerror still occurs, re-throw it.Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
This is very difficult for anyone to verify that it solves the original problem from #23131, because that error only seems to happen in certain environments.
If you can repeatedly reproduce the
EBUSYerrors, then manual testing is quite easy:Without this change:
EBUSYerror occurs.EBUSYerror continues to occur.With this change:
I also manually tested this change with some logging:
console.logstatement when anEBUSYerror occurred in thesetfunction.console.logstatement when the cache file is successfully written to after anEBUSYerror occurred.Every time the
EBUSYerror occurred, the first message was logged. The very next attempt to write (after waiting 100ms) would succeed and the second message would be logged.Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.🦋 Canary release
This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the
@storybookjs/coreteam here.core team members can create a canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>Summary by CodeRabbit
Improvements
Tests