Skip to content

fix: upstream runtime fixes made since 0.2.0 - #2

Draft
whygee-dev wants to merge 4 commits into
mainfrom
fix/upstream-monorepo-fixes
Draft

whygee-dev wants to merge 4 commits into
mainfrom
fix/upstream-monorepo-fixes

Conversation

@whygee-dev

Copy link
Copy Markdown
Owner

Summary

Four fixes made in the production copy of this VFS since 0.2.0, ported in this package's terms:

  • Allocator errors are named. When the engine supports growable SharedArrayBuffer but refuses the reservation itself, runtime creation used to say the feature was missing. It now says "could not reserve a growable SharedArrayBuffer: " and keeps the allocator error as cause.
  • Attached databases reserve no write-ahead files. Only the primary database is opened with a WriteAhead, so the -wa0/-wa1 pair given to each attached database was never read. With default options each attached database now reserves about 1 GiB less address space and commits 16 MiB less memory.
  • The clean-slate reset finishes. resetMemoryWriteAheadSharedRuntime() without a filename threw on the first runtime that still counted an open handle, leaving the runtimes before it zeroed but registered and the ones after it untouched. It now resets and deregisters every runtime. The targeted reset still refuses while its runtime has open handles, before touching any state.
  • Segments grow in bounded steps. A file past its first segment grew its buffer to the exact byte on almost every page-sized write. A growth now doubles the segment, from 64 KiB up to 1 MiB per step, or exactly what a larger write needs.

Changes a user can notice

  • MemoryWriteAheadDatabaseFiles.writeAhead is now MemoryWriteAheadSharedFile[] instead of a two-element tuple, and it is empty for an attached database. The README example registers the attached database file alone.
  • Diagnostics list one file per attached database instead of three, and allocated capacity can sit up to 1 MiB above the logical size while a segment grows.

Not ported

  • The lower default write-ahead cap (512 → 128 MiB) was tuned on one application's telemetry; maxWriteAheadCapacityBytes already lets a caller choose it.
  • Sizing an attached database from a known import size, and its follow-ups: the package has no raw-byte import, so an attached database's size is unknown when it is registered.

Validation

  • pnpm format:check, pnpm check, pnpm build: pass.
  • pnpm test:browser (Chromium): 32 passed, 5 of them new. Each new case except the targeted-reset guard was seen red with only src/memoryWriteAheadSharedRuntime.ts reverted; the guard fails when the open-handle check moves after the reset.
  • The 5 new tests and the attached-database transaction test, 10 repeats: 60/60 passed.
  • Headless Chromium, default options: appending 48 MiB in 4 KiB writes went from 12,288 grow calls and 115–150 ms to 60 calls and about 7 ms; inserting 64 MiB of rows and a TRUNCATE checkpoint went from 399–436 ms to 229–284 ms.

Not run: WebKit and Firefox (the per-page reservation cap that motivates the attached-database fix comes from iOS WebKit reports in production, not reproduced here).

…vation fails

When the engine supports growable SharedArrayBuffer but refuses the
reservation itself (for example once a page has reserved more address
space than the browser allows), runtime creation threw "MemoryWriteAheadVFS
requires growable SharedArrayBuffer support" and dropped the allocator's
error. That message points at a missing browser feature, so a user could not
tell an exhausted reservation budget from an unsupported engine.

The error now reads "MemoryWriteAheadVFS could not reserve a growable
SharedArrayBuffer: <allocator message>" and carries the allocator error as
its cause. Engines that return a non-growable buffer still get the original
message, and pages without cross-origin isolation keep their COOP/COEP hint.
ensureMemoryWriteAheadDatabaseFiles gave every attached database a -wa0/-wa1
pair next to its database file. Only the runtime's primary database is ever
opened with a WriteAhead; an attached database takes the rollback-journal
path and never reads those files. With the default options each attached
database therefore reserved an extra 1 GiB of growable SharedArrayBuffer
address space and committed 16 MiB of memory it could not use. A browser
that caps the total reservation per page (seen on iOS WebKit) refused to
attach databases sooner than it had to.

An attached database now gets its database file only, and `writeAhead` is an
empty list for it; the primary database still gets its pair. Diagnostics list
one file per attached database instead of three. The `writeAhead` type is now
`MemoryWriteAheadSharedFile[]` instead of a two-element tuple, and the README
example registers the attached database file alone.
… released

resetMemoryWriteAheadSharedRuntime() without a filename (also reached through
MemoryWriteAheadVFS.resetRuntime()) is meant to hand the next caller a clean
slate, but it threw on the first runtime that still counted an open handle.
Runtimes visited before that one were already zeroed yet stayed registered,
runtimes after it were left untouched, and the registry was never cleared.
One connection that a test forgot to close therefore made every later
createMemoryWriteAheadSharedRuntime call reuse stale, half-reset files.

The no-filename form now resets and deregisters every runtime, open handles
or not. The targeted form is unchanged: it still refuses while its runtime
has open handles, and it refuses before touching any state, which a new test
now pins.
…act byte

Once a file outgrew its first segment, every write past the end grew the next
segment's SharedArrayBuffer to exactly the bytes it needed. SQLite appends
one page at a time, so a database or write-ahead file larger than its initial
capacity resized a buffer and rebuilt its byte view on almost every write.

A growth now doubles the segment, starting at 64 KiB and adding at most 1 MiB
per step, or exactly what a larger write needs, and never past the segment's
maximum. Reads still stop at the logical file size, so the spare room is
invisible to SQLite, and cloned runtimes see the grown buffer as before.
Fixed-capacity fallback buffers are unaffected.

Measured in headless Chromium on the default options: appending 48 MiB in
4 KiB writes past a database's first segment went from 12,288 grow calls and
115-150 ms to 60 grow calls and about 7 ms; inserting 64 MiB of rows and
running a TRUNCATE checkpoint went from 399-436 ms to 229-284 ms.
Diagnostics now report allocated capacity up to 1 MiB above the logical size
for a segment that is still growing.
@whygee-dev
whygee-dev marked this pull request as draft October 7, 2026 17:04
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.

1 participant