-
Notifications
You must be signed in to change notification settings - Fork 87
OPFS Error handling improvements #304
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
Conversation
[Fix] OPFS Potential Deadlocks #56
Summary of ChangesHello @stevensJourney, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the error handling mechanisms within the OPFS (Origin Private File System) integration. It addresses two critical issues: preventing deadlocks caused by unreleased navigator locks during failed access handle creation and ensuring the "retryOps" array is consistently cleared to avoid permanent database connection failures. These changes improve the stability and resilience of database operations, particularly under stressful conditions like rapid tab opening or asynchronous operation rejections. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
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.
Code Review
This pull request introduces two important error handling improvements. In OPFSCoopSyncVFS.js, it adds logic to ensure navigator locks are released on error, preventing deadlocks. In sqlite-api.js, it ensures the retryOps array is always cleared, preventing subsequent operations from failing after a rejected promise. Both changes are well-implemented and significantly improve the robustness of the code. I have one suggestion to make the error handling even more resilient to future changes.
| } catch (e) { | ||
| this.log?.(`failed to create access handles for ${file.path}`, e); | ||
| // Close any of the potentially opened access handles | ||
| this.#releaseAccessHandle(file); |
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 #releaseAccessHandle function is declared as async, meaning it returns a promise. Although it currently contains no await statements, it's a good practice to await the call. This makes the code more robust and prevents potential race conditions if the function's implementation becomes truly asynchronous in the future.
| this.#releaseAccessHandle(file); | |
| await this.#releaseAccessHandle(file); |
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'm not sure why this method is marked as async since it doesn't contain any await statements internally, but it's probably safer to await the call here to handle the async signature correctly.
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.
@stevensJourney You're right, there's no reason for #releaseAccessHandle() to be async or contain any async code. I don't know what I was thinking there. Let's just fix that instead by:
- Removing
asyncfrom these two#releaseAccessHandle()lines. - Undoing your
awaitaddition.
Everything else LGTM!
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.
Sounds good to me :) . I've made those changes.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
Thanks for the PR! |
This ports some OPFS fixes from the PowerSync fork.
The original downstream PRs are:
PR 56: Fix navigator lock release on error in OPFSCoopSyncVFS
Problem: When opening a database with OPFSCoopSyncVFS, if creating synchronous access handles fails after acquiring a navigator lock, the lock is never released. This causes subsequent open attempts to deadlock waiting for the same lock. The issue is could be reproduced (with PowerSync) when rapidly opening multiple tabs (5+) and refreshing rapidly.
Solution: Wrap the access handle creation in a try-catch block and ensure the navigator lock is released in case of errors, preventing deadlocks.
PR 59: Clear retryOps array in finally block to prevent permanent connection failures
Problem: When an async operation in
Module.retryOpsrejects, the rejected promise remains in the array indefinitely. This causes all subsequent SQLite operations to fail immediately with the same error, permanently breaking the database connection until page refresh.Root cause: The Module.retryOps = [] cleanup only executed after successful Promise.all(), so rejected promises were never cleared.
Solution: Use a finally block to ensure
Module.retryOpsis always cleared after each retry iteration, regardless of whether promises resolve or reject. This allows each retry attempt to start with a clean state while still propagating the original error to the caller.Checklist
non-exclusive, royalty-free, irrevocable copyright license to reproduce, prepare
derivative works of, publicly display, sublicense, and distribute this
Contribution and such derivative works.
Contribution contains no content requiring a license from any third party.