Skip to content

Commit 82eff59

Browse files
stevensJourneyChriztiaan
authored andcommitted
Introduced functionality for releasing the navigator lock.
1 parent 79cf02d commit 82eff59

File tree

7 files changed

+47
-30
lines changed

7 files changed

+47
-30
lines changed

.changeset/purple-socks-smash.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@powersync/web': minor
3+
---
4+
5+
Introduced functionality for releasing the navigator lock.
6+
This resolves an issue related to sequential `connect()` calls breaking all syncing and never reaching a `connected` state. A typical error case was React's StrictMode which could trigger an `useEffect` which was calling `connect()` twice.

packages/web/src/db/adapters/WebDBAdapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
44
export type SharedConnectionWorker = {
55
identifier: string;
66
port: MessagePort;
7+
release: () => void;
78
};
89

910
export interface WebDBAdapter extends DBAdapter {

packages/web/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ResolvedWebSQLOpenOptions } from './web-sql-flags';
1010
export type SharedConnectionWorker = {
1111
identifier: string;
1212
port: MessagePort;
13+
release: () => void;
1314
};
1415

1516
export type WrappedWorkerConnectionOptions<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = {
@@ -64,7 +65,6 @@ export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLO
6465
},
6566
async () => {
6667
resolve();
67-
6868
// Free the lock when the connection is already closed.
6969
if (this.lockAbortController.signal.aborted) {
7070
return;
@@ -89,7 +89,7 @@ export class WorkerWrappedAsyncDatabaseConnection<Config extends ResolvedWebSQLO
8989
);
9090

9191
const newPort = await remote[Comlink.createEndpoint]();
92-
return { port: newPort, identifier };
92+
return { port: newPort, identifier, release: () => this.lockAbortController.abort() };
9393
}
9494

9595
/**

packages/web/src/db/sync/SharedWebStreamingSyncImplementation.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PowerSyncConnectionOptions, PowerSyncCredentials, SyncStatus, SyncStatusOptions } from '@powersync/common';
22
import * as Comlink from 'comlink';
3-
import { AbstractSharedSyncClientProvider } from '../../worker/sync/AbstractSharedSyncClientProvider';
3+
import { SharedSyncClientProvider } from '../../worker/sync/SharedSyncClientProvider';
44
import {
55
ManualSharedSyncPayload,
66
SharedSyncClientEvent,
@@ -17,20 +17,28 @@ import {
1717
* The shared worker will trigger methods on this side of the message port
1818
* via this client provider.
1919
*/
20-
class SharedSyncClientProvider extends AbstractSharedSyncClientProvider {
20+
class SharedSyncClientProviderImplementation implements SharedSyncClientProvider {
21+
protected release: (() => void) | null;
22+
2123
constructor(
2224
protected options: WebStreamingSyncImplementationOptions,
2325
public statusChanged: (status: SyncStatusOptions) => void,
2426
protected webDB: WebDBAdapter
2527
) {
26-
super();
28+
this.release = null;
2729
}
2830

2931
async getDBWorkerPort(): Promise<MessagePort> {
30-
const { port } = await this.webDB.shareConnection();
32+
const { port, release } = await this.webDB.shareConnection();
33+
this.release = release;
3134
return Comlink.transfer(port, [port]);
3235
}
3336

37+
async releaseSharedConnection() {
38+
this.release?.();
39+
this.release = null;
40+
}
41+
3442
async fetchCredentials(): Promise<PowerSyncCredentials | null> {
3543
const credentials = await this.options.remote.getCredentials();
3644
if (credentials == null) {
@@ -159,7 +167,7 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
159167
/**
160168
* Pass along any sync status updates to this listener
161169
*/
162-
this.clientProvider = new SharedSyncClientProvider(
170+
this.clientProvider = new SharedSyncClientProviderImplementation(
163171
this.webOptions,
164172
(status) => {
165173
this.iterateListeners((l) => this.updateSyncStatus(status));

packages/web/src/worker/sync/AbstractSharedSyncClientProvider.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { PowerSyncCredentials, SyncStatusOptions } from '@powersync/common';
2+
3+
/**
4+
* The client side port should provide these methods.
5+
*/
6+
export interface SharedSyncClientProvider {
7+
fetchCredentials(): Promise<PowerSyncCredentials | null>;
8+
uploadCrud(): Promise<void>;
9+
statusChanged(status: SyncStatusOptions): void;
10+
getDBWorkerPort(): Promise<MessagePort>;
11+
releaseSharedConnection(): void;
12+
13+
trace(...x: any[]): void;
14+
debug(...x: any[]): void;
15+
info(...x: any[]): void;
16+
log(...x: any[]): void;
17+
warn(...x: any[]): void;
18+
error(...x: any[]): void;
19+
time(label: string): void;
20+
timeEnd(label: string): void;
21+
}

packages/web/src/worker/sync/SharedSyncImplementation.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { LockedAsyncDatabaseAdapter } from '../../db/adapters/LockedAsyncDatabas
2525
import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags';
2626
import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection';
2727
import { getNavigatorLocks } from '../../shared/navigator';
28-
import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider';
28+
import { SharedSyncClientProvider } from './SharedSyncClientProvider';
2929
import { BroadcastLogger } from './BroadcastLogger';
3030

3131
/**
@@ -64,7 +64,7 @@ export interface SharedSyncImplementationListener extends StreamingSyncImplement
6464
*/
6565
export type WrappedSyncPort = {
6666
port: MessagePort;
67-
clientProvider: Comlink.Remote<AbstractSharedSyncClientProvider>;
67+
clientProvider: Comlink.Remote<SharedSyncClientProvider>;
6868
db?: DBAdapter;
6969
};
7070

@@ -216,7 +216,7 @@ export class SharedSyncImplementation
216216
addPort(port: MessagePort) {
217217
const portProvider = {
218218
port,
219-
clientProvider: Comlink.wrap<AbstractSharedSyncClientProvider>(port)
219+
clientProvider: Comlink.wrap<SharedSyncClientProvider>(port)
220220
};
221221
this.ports.push(portProvider);
222222

@@ -259,6 +259,7 @@ export class SharedSyncImplementation
259259
}
260260

261261
// Clearing the adapter will result in a new one being opened in connect
262+
await trackedPort.clientProvider.releaseSharedConnection();
262263
this.dbAdapter = null;
263264

264265
if (shouldReconnect) {

0 commit comments

Comments
 (0)