a73x

dd859ca9

test: the dribble is written across pumps, not in one blocking write

a73x   2026-09-03 19:16

Commit message
test: the dribble is written across pumps, not in one blocking write

The observer-cap test pushed the whole over-cap dribble in a single
writeAllFd and only then pumped. Nobody reads that socket except the
pump, so the write only returns if the send buffer swallows the lot: a
few hundred KB on Linux, about 8 KB on Darwin, which is the cap itself.
The Mac suite wedged in write with no output. The peer end is
non-blocking now and the loop alternates write and pump, which is also
what a real dribbling peer looks like, stopping the moment the daemon
drops the observer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

src/server/server_test_attach.zig
Old New
@@ -1508,7 +1508,33 @@ test "Server: an observer that dribbles a huge frame is dropped at the cap, not
1508 const dribble = try alloc.alloc(u8, srv_mod.observer_inbound_max + 1); 1508 const dribble = try alloc.alloc(u8, srv_mod.observer_inbound_max + 1);
1509 defer alloc.free(dribble); 1509 defer alloc.free(dribble);
1510 @memset(dribble, 'x'); 1510 @memset(dribble, 'x');
1511 try proto.writeAllFd(peer.handle, dribble); 1511
1512 // Dribble it the way a real peer does — write, let the daemon pump,
1513 // write again — rather than in one blocking writeAllFd. Nobody reads
1514 // this socket except `pumpOnce`, so the whole dribble has to fit in the
1515 // send buffer for a single write to return, and it does not everywhere:
1516 // a unix stream socket holds a few hundred KB on Linux and about 8 KB on
1517 // Darwin, which is the cap itself. The one write wedged the Mac suite in
1518 // `write` with no output. Non-blocking so a full buffer is a short write
1519 // to hand back to the pump instead of a stall, and the loop stops the
1520 // moment the daemon drops the observer, which is the whole point.
1521 const fl = try std.posix.fcntl(peer.handle, std.posix.F.GETFL, 0);
1522 const nb: u32 = @bitCast(std.posix.O{ .NONBLOCK = true });
1523 _ = try std.posix.fcntl(peer.handle, std.posix.F.SETFL, fl | nb);
1524 var off: usize = 0;
1525 // Bounded: every iteration either moves bytes or gives the daemon a pump
1526 // to drain them, so a run that spends this many without the drop has
1527 // stopped making progress and should fail rather than hang.
1528 for (0..1000) |_| {
1529 if (off == dribble.len or td.srv.observers[0] == null) break;
1530 off += std.posix.write(peer.handle, dribble[off..]) catch |e| switch (e) {
1531 // The drop closes the socket under us; that IS the outcome.
1532 error.WouldBlock => @as(usize, 0),
1533 error.BrokenPipe, error.ConnectionResetByPeer => break,
1534 else => return e,
1535 };
1536 try td.srv.pumpOnce(20);
1537 }
1512 1538
1513 try td.srv.pumpOnce(20); 1539 try td.srv.pumpOnce(20);
1514 try std.testing.expect(td.srv.observers[0] == null); 1540 try std.testing.expect(td.srv.observers[0] == null);