Describe the bug
If another tab has the database open and that tab is frozen, openBrowserWASQLiteOPFSDatabase() in a new tab never settles. The bundled OPFSCoopSyncVFS asks the current holder to hand over its access handles over a BroadcastChannel, then waits for the holder's Web Lock. A frozen tab can't answer. The open has no timeout or AbortSignal, so the caller can't give up and fall back to non-persisted sync. Anything waiting on the open, such as persisted collections reading their startup metadata, waits until the other tab thaws.
To Reproduce
A Vite app with @tanstack/browser-db-sqlite-persistence@0.2.23 and vite@8.3.1:
hang.html
<!doctype html>
<meta charset="utf-8" />
<title>Frozen holder repro</title>
<pre id="log">Opening…</pre>
<script type="module" src="./hang.js"></script>
hang.js
import { openBrowserWASQLiteOPFSDatabase } from "@tanstack/browser-db-sqlite-persistence";
// Tab A: load /hang.html and let it open the store. Freeze tab A, then load
// /hang.html in tab B. Tab B's open stays pending until tab A is thawed.
const log = document.querySelector("#log");
const started = performance.now();
const elapsed = () => `${((performance.now() - started) / 1000).toFixed(1)} s`;
const timer = setInterval(() => (log.textContent = `open pending for ${elapsed()}`), 250);
try {
const db = await openBrowserWASQLiteOPFSDatabase({ databaseName: "repro.sqlite" });
await db.execute("CREATE TABLE IF NOT EXISTS t (x)");
await db.execute("INSERT INTO t VALUES (1)");
log.textContent = `opened after ${elapsed()}`;
} catch (e) {
log.textContent = `failed after ${elapsed()}: ${e.name}: ${e.message}`;
}
clearInterval(timer);
- Open
/hang.html in tab A. It shows opened after 0.1 s.
- Freeze tab A by sending the DevTools protocol command
Page.setWebLifecycleState with {"state": "frozen"} to it.
- Open
/hang.html in tab B. It shows open pending for 15.0 s and keeps counting.
- Send
{"state": "active"} to tab A. Tab B shows opened after 15.1 s.
main at 4c5a8de, which includes #1844, behaves the same when I build its opfs-database.ts against the 0.2.23 worker. Freezing fires freeze, not pagehide, so the new cleanup never runs.
Expected behavior
A way to bound the wait, such as an AbortSignal or timeout option on openBrowserWASQLiteOPFSDatabase(), so the caller can fall back to non-persisted sync. An abandoned open that completes later should release the database rather than keep holding it.
Desktop:
- OS: Linux x86_64
- Browser: Chrome, headless, driven over CDP
- Version: 152.0.0.0
Additional context
Describe the bug
If another tab has the database open and that tab is frozen,
openBrowserWASQLiteOPFSDatabase()in a new tab never settles. The bundled OPFSCoopSyncVFS asks the current holder to hand over its access handles over a BroadcastChannel, then waits for the holder's Web Lock. A frozen tab can't answer. The open has no timeout orAbortSignal, so the caller can't give up and fall back to non-persisted sync. Anything waiting on the open, such as persisted collections reading their startup metadata, waits until the other tab thaws.To Reproduce
A Vite app with
@tanstack/browser-db-sqlite-persistence@0.2.23andvite@8.3.1:hang.html
hang.js
/hang.htmlin tab A. It showsopened after 0.1 s.Page.setWebLifecycleStatewith{"state": "frozen"}to it./hang.htmlin tab B. It showsopen pending for 15.0 sand keeps counting.{"state": "active"}to tab A. Tab B showsopened after 15.1 s.mainat 4c5a8de, which includes #1844, behaves the same when I build itsopfs-database.tsagainst the 0.2.23 worker. Freezing firesfreeze, notpagehide, so the new cleanup never runs.Expected behavior
A way to bound the wait, such as an
AbortSignalortimeoutoption onopenBrowserWASQLiteOPFSDatabase(), so the caller can fall back to non-persisted sync. An abandoned open that completes later should release the database rather than keep holding it.Desktop:
Additional context
OPFSCoopSyncVFS.create()runs still reach callers asOPFSWorkerRequestErrorwithcode: "INTERNAL"and only the message. fix(browser-db-sqlite-persistence): release OPFS workers on pagehide #1844 adds the VFS cause toopen_v2failures, butcreate()runs outside thattry, andhandleWorkerRequestforwardserror.messagealone. Forwardingerror.nametoo would let callers tellNoModificationAllowedErrorcontention from other failures. The navigation failure that fix(browser-db-sqlite-persistence): release OPFS workers on pagehide #1844 fixes arrived that way:Failed to execute 'removeEntry' on 'FileSystemDirectoryHandle'from the VFS's stale-directory sweep, reported upstream at OPFSCoopSyncVFS.create() fails with NoModificationAllowedError after a back/forward-cache navigation rhashimoto/wa-sqlite#362.