Fix deadlock issue with OPFS and Rust client #725
Merged
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.
With the OPFS VFS enabled, each tab uses its own dedicated worker to host the database. The shared sync worker will proxy its requests through a connection on one of these tabs.
When the tab currently hosting the database for the shared worker is closed, the worker calls
disconnect()
and is supposed to pick another tab. That works well with the JS sync implementation, but for the Rust implementation, adisconnect()
call requires aSELECT powersync_control('stop');
invocation. Since the tab is already gone, and Comlink is not handling this case, all database promises will just never resolve.Worse, since the database from the tab is wrapped in an
LockedAsyncDatabaseAdapter
, the sync worker holds a global lock on the database while waiting on a dead promise. This pretty much brings everything to a halt.As a solution, this PR makes
WorkerWrappedAsyncDatabaseConnection
aware of the fact that its remote may close. We're only using this when a shared worker requests a connection from a tab, in other cases where a tab owns a dedicated worker this can't happen.When removing a port, the shared worker will set a flag on the worker-wrapped connection that will reject all outstanding promises and deny new requests. This can cause an exception in the shared worker, but it reliably recovers from that by reconnecting with a different tab.
To reproduce the issue, apply the following path to adopt OPFS and the Rust client in the react supabase demo:
Open the demo app, login and open two additional tabs of the demo app. I'm then using a Chrome extension to reload the first two tabs. These tabs will run into a locked database and will never connect.