Files
sx/examples/1630-socket-nonblocking.sx
agra 659c43c8d6 feat: std.socket nonblocking surface — fcntl, errno, typed _nb wrappers (PLAN-HTTPZ S2)
set_nonblocking (C-variadic fcntl), errno via __error (darwin; C3
selects per-OS), and accept_nb/read_nb/write_nb returning a typed
SockErr — WouldBlock / Closed / Fault — so readiness-loop callers never
parse -1/errno pairs. EINTR retries internally; accept_nb skips
ECONNABORTED. Adds connect, shutdown, socketpair, AF_UNIX, SHUT_*.
examples/1630 pins the result algebra on a socketpair and a nonblocking
TCP listener (WouldBlock on empty backlog, accept after loopback
connect); verified under sx run AND sx build. The .ir snapshot regen is
mechanical: new std decls shift @str/@tag.str numbering and grow the
type table (179 -> 185).
2026-06-12 20:53:35 +03:00

95 lines
3.5 KiB
Plaintext

// std.socket nonblocking surface (PLAN-HTTPZ S2): set_nonblocking via
// the C-variadic fcntl, and the typed _nb wrappers' full result
// algebra — bytes, WouldBlock, Closed — on a unix socketpair, plus
// accept_nb on a nonblocking TCP listener (WouldBlock with an empty
// backlog, a connection after a loopback connect).
#import "modules/std.sx";
PORT :: 18931;
main :: () -> i32 {
// ── socketpair: read/write algebra ────────────────────────────────
pair : [2]i32 = ---;
if socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0, @pair[0]) != 0 {
print("socketpair failed\n");
return 1;
}
a := pair[0];
b := pair[1];
if !socket.set_nonblocking(a) or !socket.set_nonblocking(b) {
print("set_nonblocking failed\n");
return 1;
}
buf : [16]u8 = ---;
n, e := socket.read_nb(a, @buf[0], 16);
if e != error.WouldBlock {
print("empty pair read: expected WouldBlock\n");
return 1;
}
print("empty read: WouldBlock\n");
msg := "ping";
wn, we := socket.write_nb(b, msg.ptr, 4);
if we { print("write_nb failed\n"); return 1; }
if wn != 4 { print("short write: {}\n", wn); return 1; }
rn, re := socket.read_nb(a, @buf[0], 16);
if re { print("read_nb after write failed\n"); return 1; }
if rn != 4 { print("short read: {}\n", rn); return 1; }
print("pair round trip: {} bytes\n", rn);
socket.close(b);
cn, ce := socket.read_nb(a, @buf[0], 16);
if ce != error.Closed {
print("read after peer close: expected Closed\n");
return 1;
}
print("peer close: Closed\n");
socket.close(a);
// ── nonblocking listener: accept_nb ───────────────────────────────
lfd := socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0);
if lfd < 0 { print("listener socket failed\n"); return 1; }
one : i32 = 1;
socket.setsockopt(lfd, socket.SOL_SOCKET, socket.SO_REUSEADDR, @one, 4);
addr : socket.SockAddr = .{
sin_len = 16, sin_family = xx socket.AF_INET,
sin_port = socket.htons(PORT), sin_addr = 0x0100007F, // 127.0.0.1
};
if socket.bind(lfd, @addr, 16) != 0 { print("bind failed\n"); return 1; }
if socket.listen(lfd, 4) != 0 { print("listen failed\n"); return 1; }
if !socket.set_nonblocking(lfd) { print("listener set_nonblocking failed\n"); return 1; }
afd, ae := socket.accept_nb(lfd);
if ae != error.WouldBlock {
print("empty backlog: expected WouldBlock\n");
return 1;
}
print("empty backlog: WouldBlock\n");
// A blocking loopback connect completes the handshake against the
// listen queue without an accept having run yet.
cfd := socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0);
if cfd < 0 { print("client socket failed\n"); return 1; }
if socket.connect(cfd, @addr, 16) != 0 {
print("connect failed: errno {}\n", socket.errno());
return 1;
}
got := -1;
tries := 0;
while got < 0 and tries < 1000 {
c2, ae2 := socket.accept_nb(lfd);
if !ae2 { got = xx c2; }
else if ae2 != error.WouldBlock { print("accept_nb fault\n"); return 1; }
tries += 1;
}
if got < 0 { print("accept never produced the connection\n"); return 1; }
print("accept after connect: ok\n");
socket.close(xx got);
socket.close(cfd);
socket.close(lfd);
print("socket nonblocking ok\n");
return 0;
}