Content temporarily unavailable
PR #2213 OPFS close review#
- Base: origin/main at the PR's merge base. Original PR commit: 832dff8a4.
- Repairs: b836bfc95 (connection close), 0cd9128c8 (worker request order), d56a7ee98 (closed-connection error), and the change that gives the request-order oracle its Node and Chromium drivers.
- Oracles:
- The connection close lane of packages/browser-db-sqlite-persistence/tests/opfs-page-lifecycle-oracle.test.ts, which uses a controlled worker.
- The request-order oracle, packages/browser-db-sqlite-persistence/tests/opfs-request-order-oracle.ts. Its Node driver is tests/opfs-request-order-oracle.test.ts and its Chromium driver is e2e/opfs-request-order-oracle.opfs.spec.ts.
Contract and evidence#
Every request an OPFS connection accepts settles exactly once. Close ends admission when it starts, and concurrent close calls share one outcome. The worker answers requests in arrival order, so close waits for earlier requests and those requests keep their results. Pagehide or a Worker terminal event during close wins: close and pending work reject with that event's failure. The README states this contract.
The original PR rejected pending work when close settled. An external review found that concurrent close rejected the second caller, that work admitted during close reached a worker that had cleared its state, and that the pinned close table could not distinguish rejecting at close start from rejecting at settlement. A real-worker probe then showed that the worker handled messages concurrently: close ran while an execute waited for the OPFS access handle, SQLite refused to close the unfinalized statement, and close was answered first.
Calibration#
Each control below ran against the full lifecycle or request-order oracle. "Killed" means the named lane failed an assertion rather than timing out. Where a row names the failing observation, it was inspected; the other rows record only which lane failed.
| Control | Owner | Result |
|---|---|---|
| Pre-PR connection (pending work never settles) | Close lane | Killed at close settlement |
| Original PR connection (second close posts its own request) | Close lane | Killed |
| Reject all pending work when close starts | Close lane | Killed |
| Concurrent close not memoized | Close lane | Killed |
| No admission check while closing | Close lane | Killed |
| Defer rejection past close settlement | Close lane | Killed at close settlement |
| Ignore pagehide while closing | Close lane, pagehide lane | Killed |
| Ignore Worker error while closing | Close lane, terminal lane | Killed |
| Concurrent worker (pre-repair) | Request-order, Node driver | Killed on an insert that waits for the handle while close reaches the worker |
| Worker queues executes but not close | Request-order, Node driver | Killed on the same history |
| Concurrent worker (pre-repair) | Request-order, Chromium driver | Killed in 20 of 32 examples: settlements differ from the model |
| Worker queues executes but not close | Request-order, Chromium driver | Killed in 20 of 32 examples |
The pre-PR and original-PR connection controls are the exact earlier production files. The other connection and worker controls were temporary source mutations, reverted after each run. Before the close lane replaced it, the original pinned close table let the "reject at close start" and "dispose before reject" mutants pass.
With the repairs, the package's unit and type suite passed: 20 files and 467 runtime tests. All configured Chromium OPFS specs passed.
A first version of the shared driver returned the handle in the same turn that it submitted close. Close then never reached the worker during the wait, and both worker mutants passed both drivers. The driver now waits for close to reach the worker before it returns the handle. Both mutants then failed.
Limits#
The close lane uses a controlled worker that can answer in any order, so it also covers a close response that overtakes pending work. The request-order oracle runs the real worker module and wa-sqlite WASM in Node over an in-memory VFS with OPFSCoopSyncVFS's lock protocol, and its Chromium driver runs the same fixed examples against real OPFS in local Chromium. Together they do not establish other browsers, frozen tabs, a crash between a write and close, or multi-tab contention beyond one held handle.
BrowserWASQLiteDriver.close() does not wait for queued driver work, the same as the Node and OP-SQLite drivers. A probe showed that a transaction and queued work interrupted by close reject with the closed-connection error, and close succeeds; SQLite rolls back the open transaction. This is the accepted driver contract: draining would make close wait for a transaction callback's own application code, which may itself wait for close.
A closed connection used to report InvalidPersistedCollectionConfigError. It now reports SQLiteConnectionClosedError, exported from the core and browser packages. Work accepted before close and then cancelled still rejects with AbortError, the same as pagehide.
While writing the Chromium driver, a 63-character database name failed to open with a bare sqlite3_open_v2 error, while a 56-character name opened. The limit and its error message are untested and unrecorded elsewhere.