Repository navigation
feat(io): add positioned byte writes - #1887
Conversation
Lorenzobattistela
left a comment
There was a problem hiding this comment.
Thanks, this is a clean addition, and the fault-injection fixtures are good. Merged onto main 71ff27d, every gate is green: repo 54/54, test 1750/1750, perf 124/124, ping 81/81, safe with 0 false rejects. Two things to fix before merge:
-
Reject append handles instead of documenting them.
On Linux, pwrite and fs.writeSync(position) on an O_APPEND fd ignore the offset and append. So File.open(p, "a") then File.write_at(f, 0, [66]) on a file holding AAAA gives AAAAB on Linux, BAAA on macOS, and returns Done on both. The doc comment says "on a non-append handle", but nothing enforces it, so on Linux the bytes silently land in the wrong place. Please fail with EINVAL before writing:- C: check fcntl(fd, F_GETFL) & O_APPEND in file_write_at_run.
- JS: Node can't read the open flags back from an fd, so record append handles in effs/file_open.js (e.g. a Set of fds opened with "a", cleared in file_close) and check it in file_write_at.
Add a case to file_write_at.bend that opens with "a", expects 22, and checks the file is unchanged.
-
Document partial writes in base.bend.
If a later chunk fails or makes no progress, write_at returns Fail, but the earlier chunks are already in the file (your control trials 1 and 2 show [0, 255] at 17-18 after Fail 5/13). The PR description says this, but the def comment doesn't. One line is enough, e.g. "A Fail may leave a prefix of the bytes written."
Optional: file_write_at_run copies file_write_bytes_run except for w->made and the call function. A shared run helper (like file_read_start in file_read.c) would remove the copy and the per-mode wrappers.
|
@Lorenzobattistela addressed both points, lmk if anything else is needed. |
#1887 added File.write_at on file_write_buffer, which no longer flushes held stdout itself, so on JS the positioned write ran before the text IO.print held. It now parks like File.write and File.write_bytes, keeping its Fail(22) for bad bytes and append files. Also: process_run names its parameters, the comment above file_open goes, and the FIFO test fails at setup if mkfifo, pipe or fork fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem and fix
r+permits sequential overwrites, but Base has no positioned write that preserves the sequential cursor. AddFile.write_at(file, offset, bytes)using Cpwriteand an explicit JSfs.writeSyncposition. Related to #1843.Append handles now return EINVAL before writing, including empty data, avoiding Linux silently appending at the wrong position. C checks descriptor flags; JS records append opens and clears them on close. The Base comment documents that a later failure can leave a written prefix.
Blast radius
One additive Base definition, the existing C/JS file-write effects, JS open/close tracking, and one shared JS-runtime Set in
comp.tsso separately scoped effect sources can share flags. Four regression files; ten files overall. No parser, emitter logic, dependency, gate, or cap changes. Existing sequential writes and #1817's shipped stdout flush remain intact.Starting offsets remain
U32; this does not complete #1843's 64-bit offsets/sizes or sync proposal. No atomicity, rollback, or durability guarantee is added.Regression evidence
The ordinary fixture covers binary bytes, unchanged neighbors, cursor preservation, EOF growth, holes, empty writes, byte validation, and handle reuse after failure. Its append extension rejects nonempty and empty positioned writes, verifies unchanged content after an allowed sequential append, and reopens for a successful positioned write. This extension fails against the previous PR head
5d90248on source CLI, native C, Bun, and Node.The controlled fixture exercises short writes, C EINTR retries, zero progress, and errors after partial progress. Independent mutations detect missing JS append registration, missing close cleanup, and misclassifying a failed C flags query as append.
Checks
ttok 0.3;git diff --checkpasses.r+, byte-path, and stdout-order regressions pass source CLI, native C, emitted Bun, and emitted Node on macOS arm64.open_bytes, empty append writes, exact descriptor reuse, foreign handles without an open effect, and closed-descriptor errors. The gate-style--checkupaggregate also passes with both new fixtures, interrupted writes, and stdout ordering.Validation limits
Local macOS uses Bun 1.3.11, Node 26.8.2, and clang 23.1.4; Linux uses Node 26.12.1 and clang 14.0.6. Full mini-cluster test/performance/ping gates, GPU, Windows, x86-64 Linux, and Linux Bun were not run for this revision. Short/EINTR/zero-progress controls use fault injection; ordinary file operations and append rejection were observed through the OS. JS tracks append flags for handles opened through Bend; flags changed by foreign code are outside this check.
Existing
file_open_modepasses three macOS paths; its Node error-message mismatch reproduces unchanged on the baseline. The foreign control fixture's--check-onlyreports foreign-dependent definitions; its executable paths pass. #1817 is now merged and included in the tested branch. Older #1601's complete integration remains unverified.