224e0465
test: e2e — one peer holding half a frame does not stop muxd stats
a73x 2026-08-27 13:22
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -94,6 +94,16 @@ done | |||
| 94 | echo " daemon can still report a command's real exit code" | 94 | echo " daemon can still report a command's real exit code" |
| 95 | exit 1; } | 95 | exit 1; } |
| 96 | 96 | ||
| 97 | # And a sixth: the half-frame leg has to put ONE byte on a unix socket and | ||
| 98 | # hold the connection open, which no shell builtin can do. python3 is the | ||
| 99 | # one tool every box this suite runs on already has; a skip here would let | ||
| 100 | # a daemon that blocks on a slow peer pass green. | ||
| 101 | command -v python3 > /dev/null 2>&1 || { | ||
| 102 | echo "e2e FAIL: this suite needs python3 (the half-frame scenario opens a raw" | ||
| 103 | echo " unix socket); install it, or lose the check that one stalled" | ||
| 104 | echo " peer cannot stop the daemon" | ||
| 105 | exit 1; } | ||
| 106 | |||
| 97 | # Where the scenarios live. This file is the runner: it hands the group | 107 | # Where the scenarios live. This file is the runner: it hands the group |
| 98 | # files a hermetic environment and an armed trap, sources them in order, | 108 | # files a hermetic environment and an armed trap, sources them in order, |
| 99 | # and owns the count pin at the bottom. It is deliberately the only place | 109 | # and owns the count pin at the bottom. It is deliberately the only place |
| @@ -167,8 +177,8 @@ done | |||
| 167 | # one of those and adds a convergence point would be pinning a fact every | 177 | # one of those and adds a convergence point would be pinning a fact every |
| 168 | # leg above already establishes. | 178 | # leg above already establishes. |
| 169 | 179 | ||
| 170 | [ "$OK_COUNT" = "82" ] || { | 180 | [ "$OK_COUNT" = "83" ] || { |
| 171 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 82 —" | 181 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 83 —" |
| 172 | echo " a scenario was added (update the pin) or silently lost" | 182 | echo " a scenario was added (update the pin) or silently lost" |
| 173 | exit 1 | 183 | exit 1 |
| 174 | } | 184 | } |
test/e2e_01_boot.sh
| Old | New | ||
|---|---|---|---|
| @@ -963,3 +963,45 @@ assert_converged "$OUT.g9" "$SOCK11" "goal commands" | |||
| 963 | softkill "$GPID" || true | 963 | softkill "$GPID" || true |
| 964 | GPID="" | 964 | GPID="" |
| 965 | ok "keygen + start --quic + mux quic:// with no --key anywhere" | 965 | ok "keygen + start --quic + mux quic:// with no --key anywhere" |
| 966 | |||
| 967 | # --- one stalled peer cannot stop the daemon -------------------------------- | ||
| 968 | # A connection that writes the first byte of a frame and then holds still. | ||
| 969 | # Before this was fixed the daemon blocked in read() on that fd and every | ||
| 970 | # other connection — stats included — waited behind it (2026-08-27, found | ||
| 971 | # on a live wall). The witness is `muxd stats` answering within its timeout | ||
| 972 | # WHILE the half frame is still outstanding; the peer is only released after. | ||
| 973 | SOCKHF="${TMPDIR:-/tmp}/muxd-e2e-halfframe-$$.sock" | ||
| 974 | start_daemon "$SOCKHF" "$OUT.hf.d" "half-frame daemon never bound" --shell /bin/sh | ||
| 975 | DHFPID=$DPID | ||
| 976 | HFMARK="$OUT.hf.sent"; defer_rm "$HFMARK" | ||
| 977 | python3 - "$SOCKHF" "$HFMARK" <<'EOF' & | ||
| 978 | import socket, sys, time | ||
| 979 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
| 980 | s.connect(sys.argv[1]) | ||
| 981 | s.sendall(b'\x06') # stats_req's kind byte, and nothing after it | ||
| 982 | open(sys.argv[2], 'w').write('sent\n') # the byte is queued on the daemon's side | ||
| 983 | time.sleep(60) | ||
| 984 | EOF | ||
| 985 | HFPID=$! | ||
| 986 | defer_kill "$HFPID" | ||
| 987 | # The marker, not a sleep: the leg's whole precondition is that the half | ||
| 988 | # frame is ALREADY outstanding when stats connects. A timed settle that | ||
| 989 | # loses the race on a slow box asks a pre-fix daemon a question it can | ||
| 990 | # answer, and the leg goes green having tested nothing. | ||
| 991 | wait_for "$HFMARK" sent 5 || { | ||
| 992 | echo "e2e FAIL: half-frame: peer never connected"; tail -30 "$OUT.hf.d"; exit 1; } | ||
| 993 | set +e | ||
| 994 | timeout 5 "$MUXD" stats --sock "$SOCKHF" > "$OUT.hf.st" 2>&1 | ||
| 995 | HFRC=$? | ||
| 996 | set -e | ||
| 997 | [ "$HFRC" -eq 0 ] || { | ||
| 998 | echo "e2e FAIL: half-frame: stats exited $HFRC with a one-byte peer outstanding" | ||
| 999 | echo " (124 = the daemon is blocked in read() behind that peer)" | ||
| 1000 | cat "$OUT.hf.st"; tail -30 "$OUT.hf.d"; exit 1; } | ||
| 1001 | grep -q 'sessions=' "$OUT.hf.st" || { | ||
| 1002 | echo "e2e FAIL: half-frame: stats answered but not with a stats line:"; cat "$OUT.hf.st"; exit 1; } | ||
| 1003 | softkill "$HFPID" || true | ||
| 1004 | HFPID="" | ||
| 1005 | softkill "$DHFPID" || true | ||
| 1006 | DHFPID="" | ||
| 1007 | ok "one peer holding half a frame does not stop muxd stats" | ||