Fix read/read-bytes to report errors via status out-parameter - #9
Conversation
TcpStream.read and TcpStream.read-bytes always returned Result.Success, even on I/O errors, making it impossible to distinguish errors from connection close. Apply the same status out-parameter pattern used by TlsStream: the C functions now write bytes-read (>0), 0 (clean close), or -1 (error) through an int* parameter, and the Carp wrappers check it to return Result.Error with the system error message. Also fix the identical bug in UnixStream.read and UnixStream.read-bytes.
There was a problem hiding this comment.
Build & Tests
Both CI checks pass (macOS + Ubuntu). Built and ran tests locally — all 12 tests pass (6 TCP + 6 Unix).
Findings
I investigated several potential issues; all turned out to be non-problems:
-
Mutation through
&Intin Carp FFI — works correctly. Thelet [status 0 ...]pattern with&statuspassed to a C function that writes through the pointer is the standard Carp FFI out-parameter pattern. Carp's&inregisterdeclarations maps to a C pointer, and stack-allocatedletbindings are mutable at the C level. The value is correctly visible after the call. -
No buffer memory leak on error path. The C functions return the allocated buffer even on error. On the Carp side,
datais bound in the sameletblock and Carp's ownership system will call the deleter when scope exits, regardless of whichifbranch is taken. -
errnopreserved forSystem.error-text. Between the Cread()setting errno andSystem.error-textreading it, only the integer comparison(< status 0)intervenes, which doesn't touch errno. -
read-appendverified unchanged and correct. It already returns-1through its int return value, and the Carp wrapper already checks for it. -
No CHANGELOG exists in this repo — nothing to update.
-
Status cast bounded.
SOCK_BUF_SIZEis 4096, soris always withinintrange.
The only thing I couldn't verify: the PR says this matches the TLS library pattern, but there's no TLS code in this repo to compare against. Taking it at face value.
Verdict: merge
This fixes a real bug — read/read-bytes silently swallowed all I/O errors by always returning Result.Success. The fix is clean, symmetric across TcpStream and UnixStream, and the C/Carp interface is idiomatic. No regressions.
What
TcpStream.readandTcpStream.read-bytesalways returnedResult.Success, even when the underlyingread(2)syscall failed. This made it impossible to distinguish a read error from a clean connection close (both returned an empty string/array wrapped inSuccess).How
Adds an
int *statusout-parameter to the C functionsTcpStream_read_andTcpStream_read_MINUS_bytes_, matching the pattern already used byTlsStream.readin the tls library:> 0: bytes read (success)0: clean connection close (EOF)-1: error (errnois set)The Carp wrappers pass a local
statusvariable and check it to returnResult.Errorwith the system error message on failure.Also applies the same fix to
UnixStream.readandUnixStream.read-bytes, which had the identical bug.Notes
(Fn [&TcpStream] (Result String String))/(Fn [&TcpStream] (Result (Array Byte) String)).Result.Successcontinues to work. Code matchingResult.Errorwill now actually fire on read errors.read-appendwas already correct (it returned-1through the int return value).Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.