Skip to content

fix(harness): complete short writes - #4

Merged
pi0 merged 1 commit into
pithings:mainfrom
onmax:fix/harness-short-writes
Jul 29, 2026
Merged

fix(harness): complete short writes#4
pi0 merged 1 commit into
pithings:mainfrom
onmax:fix/harness-short-writes

Conversation

@onmax

@onmax onmax commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • complete createLoopback().writeFile() when a Node-compatible handle reports a legitimate short write
  • advance the input offset and explicit file position together
  • reject impossible write progress as EIO instead of hanging or returning false success
  • cover short and invalid progress with deterministic custom-driver regressions

Addresses Group G item 1 in #1.

Scope

This is only the src/harness.ts whole-file helper fix from G1. It does not include G2-G5, transport write paths, driver performance changes, public-surface changes, or work from other pull requests.

Original intent

FsDriver is deliberately a structural subset of node:fs/promises, and FileHandleLike.write() returns bytesWritten because a successful write can be short. Issue #1 asks this helper to loop like its existing readFile() counterpart. Node's own whole-file implementation also awaits and advances across repeated writes.

The guard accepts every positive integer count within the requested remainder. A zero, negative, fractional, or oversized result violates the driver contract; failing it promptly prevents an infinite loop or skipped bytes.

Before and after

Against base ffc0f66022f53a20ac57edb8c54af4ba4948f49d, an external custom driver limited to two bytes per call produced "ab" from "abcdefgh" and received one call:

{"actual":"ab","calls":[[0,2,0]]}

After this change, the packed package installed into a fresh external project produces all bytes through advancing calls:

{"actual":"abcdefgh","calls":[[0,2,0],[2,2,2],[4,2,4],[6,2,6]]}

That packed external consumer is the relevant E2E boundary. FUSE and NFS sessions call file handles directly and do not invoke Loopback.writeFile(), so a real mount would not exercise this fix.

Verification

  • pnpm exec vitest run test/index.test.ts — 18 passed
  • pnpm test — lint, typecheck, 888 passed and 197 skipped
  • pnpm build
  • packed-package external consumer in a fresh process
  • independent Standards and Spec reviews with Ponytail and simplification checks; no actionable findings

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

@onmax is attempting to deploy a commit to the unjs Team on Vercel.

A member of the Team first needs to authorize it.

@pi0x pi0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Verified independently rather than from the description: applied the branch locally and ran the suite on this host — test/index.test.ts 18 passed, full pnpm test green (lint, typecheck, 900 passed / 185 skipped). The count differs from the 888/197 in the description only because 12 Tier-2 tests gate on fusermount3 and ran here instead of skipping; the 1085 total matches.

What I checked in the change itself:

  • Offset and position advance together. handle.write(bytes, written, remaining, written) is the correct shape, and the test asserts it rather than only asserting the final content — [[0,2,0],[2,2,2],[4,1,4]] pins both the buffer offset and the explicit position, which is the part that would silently regress otherwise.
  • The guard covers every way the loop could go wrong. !Number.isInteger catches NaN/Infinity as well as fractions, <= 0 prevents the infinite loop, > remaining prevents skipping past the end. EIO is the right errno for a driver that violated the contract.
  • Zero-length is unchangedbyteLength === 0 never enters the loop, and open(path, "w") still creates and truncates, so empty-file behaviour is identical to before.
  • Symmetry with readFile is the point. The existing readFile already loops on short reads; this makes the pair consistent, which is exactly what the issue asked for.

Coverage is better than the diff suggests: test/conformance.ts calls fs.writeFile 45 times across every driver column, so the non-short path of this loop is exercised broadly by Tier 0 — the new tests only need to cover the short and invalid cases, which they do.

Two non-blocking notes, neither worth holding the merge for:

  1. Destructuring const { bytesWritten } = await handle.write(...) throws a raw TypeError if a driver returns nothing, where the old code silently tolerated it. That is out of contract (FileHandleLike.write is typed Promise<WriteResult>) and readFile has the identical exposure one line up — so matching it is the right call, not a defect. Worth knowing only because this helper is what driver authors debug against.
  2. The description's note that FUSE and NFS sessions never call Loopback.writeFile() checks out — no match anywhere in src/fuse/ or src/nfs/. So this is a driver-authoring and conformance-surface fix, not a mount-path one, and the scoping to G1 alone is right.

Tracking issue updated.

@pi0
pi0 merged commit 65ebacc into pithings:main Jul 29, 2026
5 of 6 checks passed
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.

3 participants