Skip to content

Conversation

simolus3
Copy link
Contributor

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, a disconnect() call requires a SELECT 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:

diff --git a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx
index 8a3f3c20..b3521c00 100644
--- a/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx
+++ b/demos/react-supabase-todolist/src/components/providers/SystemProvider.tsx
@@ -3,7 +3,15 @@ import { AppSchema, ListRecord, LISTS_TABLE, TODOS_TABLE } from '@/library/power
 import { SupabaseConnector } from '@/library/powersync/SupabaseConnector';
 import { CircularProgress } from '@mui/material';
 import { PowerSyncContext } from '@powersync/react';
-import { createBaseLogger, DifferentialWatchedQuery, LogLevel, PowerSyncDatabase } from '@powersync/web';
+import {
+  createBaseLogger,
+  DifferentialWatchedQuery,
+  LogLevel,
+  PowerSyncDatabase,
+  SyncClientImplementation,
+  WASQLiteOpenFactory,
+  WASQLiteVFS
+} from '@powersync/web';
 import React, { Suspense } from 'react';
 import { NavigationPanelContextProvider } from '../navigation/NavigationPanelContext';
 
@@ -12,9 +20,13 @@ export const useSupabase = () => React.useContext(SupabaseContext);
 
 export const db = new PowerSyncDatabase({
   schema: AppSchema,
-  database: {
-    dbFilename: 'example.db'
-  }
+  database: new WASQLiteOpenFactory({
+    dbFilename: 'example.db',
+    vfs: WASQLiteVFS.OPFSCoopSyncVFS,
+    flags: {
+      enableMultiTabs: typeof SharedWorker !== 'undefined'
+    }
+  })
 });
 
 export type EnhancedListRecord = ListRecord & { total_tasks: number; completed_tasks: number };
@@ -68,7 +80,7 @@ export const SystemProvider = ({ children }: { children: React.ReactNode }) => {
     const l = connector.registerListener({
       initialized: () => {},
       sessionStarted: () => {
-        powerSync.connect(connector);
+        powerSync.connect(connector, { clientImplementation: SyncClientImplementation.RUST });
       }
     });

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.

Copy link

changeset-bot bot commented Sep 22, 2025

🦋 Changeset detected

Latest commit: 8efff94

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@powersync/web Patch
@powersync/diagnostics-app Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@simolus3 simolus3 changed the title Opfs issue Fix deadlock issue with OPFS and Rust client Sep 22, 2025
@simolus3 simolus3 marked this pull request as ready for review September 23, 2025 14:29
stevensJourney
stevensJourney previously approved these changes Sep 23, 2025
Copy link
Collaborator

@stevensJourney stevensJourney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this solution. Happy with the changes here from my side.

@simolus3 simolus3 merged commit c9c1e24 into main Sep 23, 2025
9 checks passed
@simolus3 simolus3 deleted the opfs-issue branch September 23, 2025 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants