diff --git a/apps/web/src/common/sqlite/shared-service.ts b/apps/web/src/common/sqlite/shared-service.ts index b64fce1ed..6b78c8d54 100644 --- a/apps/web/src/common/sqlite/shared-service.ts +++ b/apps/web/src/common/sqlite/shared-service.ts @@ -18,6 +18,7 @@ along with this program. If not, see . */ import SharedWorker from "./shared-service.worker.ts?sharedworker"; +import { Mutex } from "async-mutex"; const sharedWorker = globalThis.SharedWorker ? new SharedWorker({ @@ -39,6 +40,7 @@ export class SharedService extends EventTarget { // This is client state to track the provider. The provider state is // mostly managed within activate(). #providerPort?: Promise; + #providerPortMutex = new Mutex(); providerCallbacks: Map< string, { @@ -64,6 +66,7 @@ export class SharedService extends EventTarget { this.#clientChannel.addEventListener( "message", async ({ data }) => { + console.log("got message from provider", data); if ( data?.type === "provider" && data?.sharedService === this.serviceName && @@ -74,6 +77,7 @@ export class SharedService extends EventTarget { // Discard any old provider and connect to the new one. this.#providerPort?.then((port) => port?.close()); this.#providerPort = this.#providerChange(); + this.#providerPortMutex.release(); await this.#resendPendingCallbacks(); } }, @@ -83,6 +87,7 @@ export class SharedService extends EventTarget { type: "client", sharedService: this.serviceName }); + this.#providerPortMutex.acquire(); window.addEventListener("beforeunload", () => { this.close(); @@ -104,9 +109,11 @@ export class SharedService extends EventTarget { const LOCK_NAME = `SharedService-${this.serviceName}`; navigator.locks .request(LOCK_NAME, { signal: this.#onDeactivate.signal }, async () => { + console.time("getting provider port"); // Get the port to request client ports. const { port, onclose } = await portProviderFunc(); port.start(); + console.timeEnd("getting provider port"); // Listen for client requests. A separate BroadcastChannel // instance is necessary because we may be serving our own @@ -168,6 +175,7 @@ export class SharedService extends EventTarget { { signal: this.#onDeactivate?.signal } ); + console.log("sending message to clients", providerId, this.serviceName); // Tell everyone that we are the new provider. broadcastChannel.postMessage({ type: "provider", @@ -207,9 +215,11 @@ export class SharedService extends EventTarget { } async #getClientId() { + console.time("getting client id"); // Use a Web Lock to determine our clientId. const nonce = Math.random().toString(); const clientId = await navigator.locks.request(nonce, async () => { + console.log("got clientid lock"); const { held } = await navigator.locks.query(); return held?.find((lock) => lock.name === nonce)?.clientId; }); @@ -231,6 +241,7 @@ export class SharedService extends EventTarget { sharedWorker?.port.start(); sharedWorker?.port.postMessage({ clientId }); + console.timeEnd("getting client id"); return clientId; } @@ -364,6 +375,10 @@ export class SharedService extends EventTarget { })(); async getProviderPort() { + console.log("waiting for port provider to become available"); + await this.#providerPortMutex.waitForUnlock(); + console.log("port provider ready."); + let tries = 0; let providerPort = await this.#providerPort; while (!providerPort) { @@ -371,7 +386,10 @@ export class SharedService extends EventTarget { throw new Error("Could not find a provider port to communicate with."); providerPort = await this.#providerPort; - console.warn("Provider port not found. Retrying in 50ms..."); + console.warn( + "Provider port not found. Retrying in 50ms...", + this.#providerPort + ); await new Promise((resolve) => setTimeout(resolve, 50)); } return providerPort; diff --git a/apps/web/src/common/sqlite/shared-service.worker.ts b/apps/web/src/common/sqlite/shared-service.worker.ts index 92f011629..7a6c6cdad 100644 --- a/apps/web/src/common/sqlite/shared-service.worker.ts +++ b/apps/web/src/common/sqlite/shared-service.worker.ts @@ -26,11 +26,13 @@ declare var self: SharedWorkerGlobalScope & typeof globalThis; const mapClientIdToPort: Map = new Map(); self.addEventListener("connect", (event) => { + console.log("connected", event); // The first message from a client associates the clientId with the port. const workerPort = event.ports[0]; workerPort.addEventListener( "message", (event) => { + console.log("received message", event.data); mapClientIdToPort.set(event.data.clientId, workerPort); // Remove the entry when the client goes away, which we detect when @@ -43,6 +45,7 @@ self.addEventListener("connect", (event) => { // Subsequent messages will be forwarded. workerPort.addEventListener("message", (event) => { const port = mapClientIdToPort.get(event.data.clientId); + console.log("sending message to client", event.data.clientId, port); port?.postMessage(event.data, [...event.ports]); }); },