c51815d8
test: the repeated-end test pins that the shell outlives the grace
a73x 2026-09-04 10:16
Commit message
src/server/server_test_session.zig
| Old | New | ||
|---|---|---|---|
| @@ -1666,9 +1666,12 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1666 | const r = (try awaitFrame(alloc, &td.srv, c.handle, .end_reply, 200)) orelse return error.NoEndReply; | 1666 | const r = (try awaitFrame(alloc, &td.srv, c.handle, .end_reply, 200)) orelse return error.NoEndReply; |
| 1667 | defer r.deinit(alloc); | 1667 | defer r.deinit(alloc); |
| 1668 | try std.testing.expect((proto.parseEndReply(r.payload) orelse return error.BadEndReply).accepted); | 1668 | try std.testing.expect((proto.parseEndReply(r.payload) orelse return error.BadEndReply).accepted); |
| 1669 | // The clock the assertions below read, started where the daemon started | 1669 | // The clock the assertions below read, started where the daemon starts |
| 1670 | // its own: `endSession` arms the deadline when it accepts. | 1670 | // its own: `endSession` arms the deadline when it accepts. MONOTONIC, and |
| 1671 | const t_end = std.time.milliTimestamp(); | 1671 | // deliberately so — the deadline being graded is `monoMs() + grace`, so a |
| 1672 | // calendar step landing between the two reads would fail a daemon that | ||
| 1673 | // did exactly the right thing. | ||
| 1674 | var since_end = try std.time.Timer.start(); | ||
| 1672 | 1675 | ||
| 1673 | // An observer nagging faster than the grace: if each accept restarted the | 1676 | // An observer nagging faster than the grace: if each accept restarted the |
| 1674 | // clock, the shell would outlive every deadline it was ever given. | 1677 | // clock, the shell would outlive every deadline it was ever given. |
| @@ -1683,7 +1686,7 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1683 | try td.srv.pumpOnce(20); | 1686 | try td.srv.pumpOnce(20); |
| 1684 | std.Thread.sleep(60 * std.time.ns_per_ms); | 1687 | std.Thread.sleep(60 * std.time.ns_per_ms); |
| 1685 | } | 1688 | } |
| 1686 | const outlived_ms = std.time.milliTimestamp() - t_end; | 1689 | const outlived_ms = since_end.read() / std.time.ns_per_ms; |
| 1687 | try std.testing.expect(!alive(pid)); | 1690 | try std.testing.expect(!alive(pid)); |
| 1688 | 1691 | ||
| 1689 | // A dead shell is only half the claim, and it was the only half asserted: | 1692 | // A dead shell is only half the claim, and it was the only half asserted: |
| @@ -1694,14 +1697,25 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1694 | // suite running faster. So the duration is the pin now. A wall clock and | 1697 | // suite running faster. So the duration is the pin now. A wall clock and |
| 1695 | // not the loop counter above, which counts nominal turns rather than time. | 1698 | // not the loop counter above, which counts nominal turns rather than time. |
| 1696 | // | 1699 | // |
| 1697 | // The LOWER bound is the real one: living past the grace is what proves | 1700 | // The LOWER bound is the property, and the only one of the two that is: |
| 1698 | // the shell ignored every TERM and left to the SIGKILL. The upper bound | 1701 | // living past the grace is what proves the shell ignored every TERM and |
| 1699 | // catches a deadline that moved a few turns; a deadline that moved | 1702 | // left to the SIGKILL. |
| 1700 | // FOREVER is already caught by the loop's own budget and the `!alive` | 1703 | // |
| 1701 | // above, so this one is deliberately loose rather than a timing gate. | 1704 | // The UPPER bound is a rail on the number, not a second pin, and it is |
| 1702 | // Measured 2026-09-04 against a 500 ms grace: 661 ms on Linux and 668 ms | 1705 | // worth saying which regression it does NOT catch. The one this test is |
| 1703 | // on macOS under `make check`. A bound at 2x would sit 330 ms above that, | 1706 | // named for is the `== null` guard dropped at the arm in `endSession`, |
| 1704 | // which is a flake waiting for a slower box. | 1707 | // and that guard's absence RESETS `end_by_ms` to now plus a grace on |
| 1708 | // every accepted nag rather than adding one — so a deadline re-armed | ||
| 1709 | // every 80 ms never fires at all, the shell outlives the loop's whole | ||
| 1710 | // 3 s budget, and the `!alive` above is what fails. No multiplier here | ||
| 1711 | // reaches that. What this bound does catch is a deadline that drifted a | ||
| 1712 | // few turns of the loop, which is why it exists and why it is loose. | ||
| 1713 | // | ||
| 1714 | // 3x because the measurement it must clear is 661 ms on Linux and 668 to | ||
| 1715 | // 682 ms on macOS under `make check` (2026-09-04) against a 500 ms grace, | ||
| 1716 | // leaving about 2.2x. Not tighter: what varies is how long one turn of | ||
| 1717 | // the loop takes to notice the kill, and a loaded box stretches both its | ||
| 1718 | // sleep and its pump. | ||
| 1705 | if (outlived_ms < Pty.term_grace_ms) { | 1719 | if (outlived_ms < Pty.term_grace_ms) { |
| 1706 | std.debug.print( | 1720 | std.debug.print( |
| 1707 | "the shell died {d} ms after the accepted end, INSIDE the {d} ms grace: " ++ | 1721 | "the shell died {d} ms after the accepted end, INSIDE the {d} ms grace: " ++ |
| @@ -1710,9 +1724,9 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1710 | ); | 1724 | ); |
| 1711 | return error.ShellDiedInsideTheGrace; | 1725 | return error.ShellDiedInsideTheGrace; |
| 1712 | } | 1726 | } |
| 1713 | if (outlived_ms >= 4 * Pty.term_grace_ms) { | 1727 | if (outlived_ms >= 3 * Pty.term_grace_ms) { |
| 1714 | std.debug.print( | 1728 | std.debug.print( |
| 1715 | "the shell lived {d} ms after the accepted end, past 4x the {d} ms grace: " ++ | 1729 | "the shell lived {d} ms after the accepted end, past 3x the {d} ms grace: " ++ |
| 1716 | "the nagging looks to have pushed the deadline out\n", | 1730 | "the nagging looks to have pushed the deadline out\n", |
| 1717 | .{ outlived_ms, Pty.term_grace_ms }, | 1731 | .{ outlived_ms, Pty.term_grace_ms }, |
| 1718 | ); | 1732 | ); |