e4e6dbee
test: soak persistence phase — one daemon, N client lifecycles, RSS/fd bounds
a73x 2026-08-14 09:09
Commit message
test/soak.sh
| Old | New | ||
|---|---|---|---|
| @@ -56,6 +56,67 @@ while [ "$i" -le "$N" ]; do | |||
| 56 | fi | 56 | fi |
| 57 | i=$((i + 1)) | 57 | i=$((i + 1)) |
| 58 | done | 58 | done |
| 59 | # ---- persistence phase (hygiene kit, 6b) ---- | ||
| 60 | # One daemon, SOAK_CYCLES client lifecycles. Catches what the run loop | ||
| 61 | # structurally cannot: C-side growth, fd leaks, unbounded accumulation — | ||
| 62 | # the classes the Zig-side LEAK marker (6a) never sees. Baseline is taken | ||
| 63 | # AFTER a warmup: the first attaches pay one-time allocations (grid, | ||
| 64 | # history) that are capacity, not leakage. | ||
| 65 | PSOCK="$TMP/mux-soak-persist-$$.sock" | ||
| 66 | PLOG="$TMP/mux-soak-persist-$$.log" | ||
| 67 | CYCLES="${SOAK_CYCLES:-20}" | ||
| 68 | WARMUP=3 | ||
| 69 | RSS_BOUND_KB=4096 | ||
| 70 | "$MUXD" run --sock "$PSOCK" --shell /bin/sh > "$PLOG" 2>&1 & | ||
| 71 | PDPID=$! | ||
| 72 | _i=0 | ||
| 73 | while [ ! -S "$PSOCK" ] && [ "$_i" -lt 100 ]; do sleep 0.05; _i=$((_i + 1)); done | ||
| 74 | if [ ! -S "$PSOCK" ]; then | ||
| 75 | echo "soak FAIL: persistence daemon never bound $PSOCK" | ||
| 76 | FAILED=$((FAILED + 1)) | ||
| 77 | printf 'persistence: daemon never bound\n' >> "$SUMMARY" | ||
| 78 | else | ||
| 79 | BASE_RSS=0; BASE_FD=0; RSS=0; FD=0 | ||
| 80 | c=1 | ||
| 81 | while [ "$c" -le "$CYCLES" ]; do | ||
| 82 | { printf 'echo cycle-%s\n' "$c"; sleep 1; printf '\034'; } | \ | ||
| 83 | timeout 30 "$MUX" --sock "$PSOCK" > /dev/null 2>&1 | ||
| 84 | RSS=$(awk '/VmRSS/{print $2}' "/proc/$PDPID/status" 2>/dev/null || echo 0) | ||
| 85 | FD=$(ls "/proc/$PDPID/fd" 2>/dev/null | wc -l) | ||
| 86 | [ "$c" -eq "$WARMUP" ] && { BASE_RSS=$RSS; BASE_FD=$FD; } | ||
| 87 | c=$((c + 1)) | ||
| 88 | done | ||
| 89 | echo "soak persistence: $CYCLES cycles, RSS ${BASE_RSS}->${RSS} kB, fds ${BASE_FD}->${FD}" | ||
| 90 | if [ "$RSS" -eq 0 ] || [ "$BASE_RSS" -eq 0 ]; then | ||
| 91 | echo "soak FAIL: persistence daemon died mid-phase" | ||
| 92 | FAILED=$((FAILED + 1)) | ||
| 93 | printf 'persistence: daemon died mid-phase\n' >> "$SUMMARY" | ||
| 94 | else | ||
| 95 | # fds must RETURN to baseline exactly: every attach opens, every | ||
| 96 | # detach must close. RSS gets a bound, not equality — allocators | ||
| 97 | # retain pages — but growth past it over this few cycles is a leak. | ||
| 98 | if [ "$FD" -ne "$BASE_FD" ]; then | ||
| 99 | echo "soak FAIL: persistence fd count $BASE_FD -> $FD across detached cycles" | ||
| 100 | FAILED=$((FAILED + 1)) | ||
| 101 | printf 'persistence: fd leak %s->%s\n' "$BASE_FD" "$FD" >> "$SUMMARY" | ||
| 102 | fi | ||
| 103 | if [ $((RSS - BASE_RSS)) -gt "$RSS_BOUND_KB" ]; then | ||
| 104 | echo "soak FAIL: persistence RSS grew $((RSS - BASE_RSS)) kB (bound $RSS_BOUND_KB)" | ||
| 105 | FAILED=$((FAILED + 1)) | ||
| 106 | printf 'persistence: RSS grew %s kB\n' "$((RSS - BASE_RSS))" >> "$SUMMARY" | ||
| 107 | fi | ||
| 108 | fi | ||
| 109 | kill "$PDPID" 2>/dev/null | ||
| 110 | wait "$PDPID" 2>/dev/null | ||
| 111 | # The daemon's own Zig-side verdict rides along for free (6a). | ||
| 112 | if grep -q "LEAK:" "$PLOG"; then | ||
| 113 | echo "soak FAIL: persistence daemon reported leaked allocations:" | ||
| 114 | grep "LEAK:" "$PLOG" | ||
| 115 | FAILED=$((FAILED + 1)) | ||
| 116 | printf 'persistence: LEAK marker\n' >> "$SUMMARY" | ||
| 117 | fi | ||
| 118 | fi | ||
| 119 | rm -f "$PLOG" "$PSOCK" | ||
| 59 | rm -f "$LOG" | 120 | rm -f "$LOG" |
| 60 | echo "---" | 121 | echo "---" |
| 61 | if [ "$FAILED" -eq 0 ]; then | 122 | if [ "$FAILED" -eq 0 ]; then |