Add non-blocking TLS operations - #4
Conversation
Add set-nonblocking, send-nb, and read-append-nb to TlsStream, matching the sockets library convention: send-nb returns 0 on would-block, read-append-nb returns -2 (read-blocked sentinel) on would-block. Handles SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE from OpenSSL, which occur during TLS renegotiation or when the underlying socket buffer is full/empty. These are prerequisites for integrating TLS with event loops (e.g. web framework HTTPS support).
There was a problem hiding this comment.
Build & Tests
Build: pass — compiles clean locally and in CI (macOS + Ubuntu).
Tests: pass — 19 total (3 new non-blocking tests), all passing locally and in CI.
Findings
1. C implementation is correct
Reviewed the full C code in src/tls_stream.h:
-
set-nonblocking(line 293): usesfcntl(F_GETFL/F_SETFL, O_NONBLOCK), correct POSIX pattern. Silently ignoresfcntlfailure, consistent withset-timeoutwhich also returns void. -
send-nb(line 307): singleSSL_writeattempt, handlesSSL_ERROR_WANT_WRITEandSSL_ERROR_WANT_READ(TLS renegotiation) correctly by returning 0. Theoffset >= (int)data->lenguard correctly returns 0 when there's nothing left to send. Error path captures viacarp_tls_capture_ssl_error. -
read-append-nb(line 329): buffer growth logic is identical to the existing blockingread-append, good consistency. Returns -2 for would-block matching the sockets library convention, 0 for clean close, -1 for error. All correct.
2. Carp wrappers are consistent
The send-nb and read-append-nb wrappers in tls.carp follow the exact same if (= n -1) (Result.Error ...) (Result.Success n) pattern as all existing operations. read-blocked sentinel is defined as a constant. Documentation is thorough.
3. Tests are adequate
The loopback test exercises the full roundtrip (set-nonblocking → send-nb → retry-loop on read-append-nb), the sentinel value is checked, and set-nonblocking is smoke-tested on a valid stream. The retry loop (tries < 1000000) is fine for a loopback test with small payloads.
4. README updated
All four new API entries are in the table. Good.
5. No issues found
The code fits cleanly into the existing architecture, error handling is complete, and the API matches the sockets library convention as stated.
Verdict: merge
Clean implementation, CI green, well-tested, consistent with existing code and the sockets library API.
Summary
Adds non-blocking I/O support to
TlsStream, matching the API convention from thesocketslibrary:set-nonblocking— puts the underlying socket into non-blocking mode viafcntl(O_NONBLOCK)send-nb— non-blocking send from a byte array at an offset; returns 0 on would-block (SSL_ERROR_WANT_WRITE/WANT_READ)read-append-nb— non-blocking append-read into a growable buffer; returns -2 (read-blockedsentinel) on would-blockread-blocked— the -2 sentinel constant, same asTcpStream.read-blockedThe C implementation handles both
SSL_ERROR_WANT_READandSSL_ERROR_WANT_WRITEfrom OpenSSL, which occur not only when the socket buffer is full/empty but also during TLS renegotiation.Motivation
Prerequisite for web framework HTTPS support (carpentry-org/web#9). All current TLS I/O is blocking and cannot integrate with event loops or
Poll.Test plan
read-blockedsentinel value is -2set-nonblockingon a valid stream does not crashOpened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.