Skip to content

fix(fs): copy binary files for each target in initFiles() - #67

Merged
kazupon merged 2 commits into
mainfrom
fix/init-files-binary-copies
Oct 1, 2026
Merged

kazupon merged 2 commits into
mainfrom
fix/init-files-binary-copies

Conversation

@kazupon

@kazupon kazupon commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Closes #63

FileSystemPublisher.initFiles() now gives each target its own copy of every binary file, as writeFile() does since #61. It works with two or more targets, and the caller's ArrayBuffers stay usable.

Cause

initFiles() built one transfer list from the caller's ArrayBuffers and passed it to postMessage() of every target:

  • The first postMessage() transferred the buffers, which detached them.
  • With two or more targets, the next postMessage() threw a DataCloneError, after only the first target had received the files.
  • With one target, the caller's buffers ended up detached.

Fix

  • For each target, initFiles() copies every binary file with slice(0) and transfers only the copies, as writeFile() does.
  • The internal broadcast() no longer takes a transfer list, because one list cannot be transferred to more than one target. Targets still receive an empty transfer list, so writeFile() for text, unlink() and mkdir() post as before.
  • The JSDoc of FSInitMessage and initFiles(), the README example and the API references describe the copies.

Also fixed

The same ArrayBuffer given for two paths threw a DataCloneError ("Transfer list contains duplicate ArrayBuffer") even with one target, because the transfer list held it twice. Each path now gets its own copy.

Behavior change

The caller's ArrayBuffers are no longer transferred. Each target gets one more copy, and the caller keeps its buffers until it drops them. A caller that relied on the transfer to move the memory to a single target now keeps a copy. Usages that worked before keep working.

Vrowzer does not call initFiles(), so its behavior does not change.

Not included

  • writeFile() keeps its current copy-and-transfer behavior.
  • Values outside the type, such as typed arrays or SharedArrayBuffers, as with writeFile().
  • A target whose postMessage() throws partway: the earlier targets keep what they received, as with writeFile().

Tests

publisher.test.ts:

  • With two MessageChannel port targets, which really transfer buffers, both receive the text and binary files with the same bytes, and the caller's buffer keeps its size and content after initFiles() returns.
  • The same ArrayBuffer given for two paths reaches a port target under both paths.
  • With mock targets, the transfer list holds only the copies in the message, not the caller's buffers, and every target gets its own copies.
  • The test that checked that the caller's buffers were in the transfer list now checks the copies instead. The text-only test passes unchanged.

The four new or changed tests failed before the change: the two port tests with a DataCloneError, and the mock tests because the caller's buffers were transferred.

Validated locally with:

  • vp run test:unit: 119 files, 1,914 tests
  • vp run test:integration:vrowzer: 67 passed and 1 conditional skip, after rebuilding @vrowzer/fs, because Vrowzer's file methods go through broadcast() and writeFile()
  • oxlint (no new warnings), knip, the format check of the changed files, and typecheck
  • E2E is left to CI, since no fixture calls initFiles()

initFiles() built one transfer list from the caller's ArrayBuffers and passed it to every target. The first postMessage() detached the buffers, so with two or more targets the next one threw a DataCloneError after only the first target had received the files. With one target, the caller's buffers ended up detached. The same ArrayBuffer given for two paths also threw a DataCloneError, even with one target, because the transfer list held it twice.

As writeFile() does since #61, initFiles() now gives each target its own copy of every binary file and transfers only the copies, so the caller's ArrayBuffers stay usable. broadcast() no longer takes a transfer list, because one list cannot be transferred to more than one target. The JSDoc of FSInitMessage and initFiles() describes the copies.
The README example of initFiles() now passes a binary file as well, and notes that each target gets its own copy, as the writeFile() example does. The API references are regenerated for the JSDoc of FSInitMessage and initFiles().
@kazupon
kazupon merged commit fce16a3 into main Oct 1, 2026
4 checks passed
@kazupon
kazupon deleted the fix/init-files-binary-copies branch October 1, 2026 11:32
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.

Copy binary files for each target in FileSystemPublisher.initFiles()

1 participant