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).
This commit is contained in:
94
examples/1630-socket-nonblocking.sx
Normal file
94
examples/1630-socket-nonblocking.sx
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user