dd859ca9
test: the dribble is written across pumps, not in one blocking write
a73x 2026-09-03 19:16
Commit message
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); |