a73x

tools/valgrind-quic.sh

Ref:   Size: 4.4 KiB   History

#!/bin/sh
# tools/valgrind-quic.sh — periodic deep leak run over the QUIC/C stack.
#
# NON-GATING and wired into no build step, on purpose: valgrind runs
# 10-50x slow, which distorts every timing-sensitive path (handshakes,
# keepalives, settle windows) into flake territory. Its unique value is
# the C side — wolfSSL/ngtcp2 allocate with malloc, which the Zig
# DebugAllocator verdict (6a) never sees. Run it by hand after touching
# the QUIC stack or bumping deps/quic.
#
# Usage: tools/valgrind-quic.sh [path/to/mux]
#   (defaults to zig-out/bin — build first with `zig build -Dcpu=x86_64_v3`;
#    valgrind 3.25 decodes no AVX-512, and a native build on a machine that
#    has it SIGILLs at startup. The QUIC deps' zigcc-native wrapper already
#    pins x86_64_v3 for the same reason. Debug build is the point: Zig debug
#    builds carry valgrind client requests natively.)
set -eu
# One binary, two modes: `d` is the daemon and `a` the agent client.
MUX="${1:-zig-out/bin/mux}"
command -v valgrind >/dev/null || { echo "valgrind is not installed"; exit 1; }
[ -x "$MUX" ] || { echo "binary missing — run zig build"; exit 1; }
TMP="${TMPDIR:-/tmp}/mux-valgrind-$$"
mkdir -p "$TMP"
VGPID=""
trap '[ -n "$VGPID" ] && kill "$VGPID" 2>/dev/null || true; rm -rf "$TMP"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
# Hermetic key: keygen into a private XDG home, --key it explicitly.
XDG_CONFIG_HOME="$TMP/cfg"; export XDG_CONFIG_HOME
"$MUX" d keygen > /dev/null
KEY="$TMP/cfg/mux/key"
SOCK="$TMP/vg.sock"
# The daemon's own stdout+stderr, kept apart from valgrind's --log-file so the two
# accounts stay readable — and so the bind refusal below can be read back.
DLOG="$TMP/mux-d.log"
# The port is derived from the pid, which makes it a GUESS in two ways: this
# band is inside the ephemeral range on a box whose ip_local_port_range has
# been lowered, so an outgoing connection can already hold the number, and
# two runs whose pids are congruent mod 900 derive the same one. The daemon sets no
# SO_REUSEADDR and refuses to share a UDP port, so both show up identically —
# the daemon says `a daemon is already listening on udp ...` and exits — and
# the answer to both is the next candidate. The port this script goes on to
# use is therefore the one a daemon BOUND, never the one it first derived.
PORT=$((47000 + ($$ % 900)))
PORT_STEP=13
PORT_TRIES=8
tries=0
while :; do
    : > "$DLOG"
    valgrind --leak-check=full --error-exitcode=99 --log-file="$TMP/vg.log" \
        "$MUX" d run --sock "$SOCK" --shell /bin/sh \
        --quic "127.0.0.1:$PORT" --key "$KEY" > "$DLOG" 2>&1 &
    VGPID=$!
    # valgrind start is SLOW; give the bind a full minute. Bail early if the
    # daemon dies under valgrind instead of waiting out the whole timeout.
    i=0
    while [ ! -S "$SOCK" ] && [ "$i" -lt 600 ]; do
        kill -0 "$VGPID" 2>/dev/null || break
        sleep 0.1; i=$((i + 1))
    done
    if [ -S "$SOCK" ]; then break; fi
    # No socket. A daemon that is still RUNNING is not a port problem — it is
    # one that will not come up, and `wait`ing on it would hang this script.
    if kill -0 "$VGPID" 2>/dev/null; then
        echo "daemon never bound under valgrind (still up after ${i}x100ms):"
        cat "$DLOG" 2>/dev/null; cat "$TMP/vg.log" 2>/dev/null; exit 1
    fi
    wait "$VGPID" 2>/dev/null || true
    VGPID=""
    grep -q "listening on udp" "$DLOG" || {
        echo "daemon died under valgrind:"
        cat "$DLOG" 2>/dev/null; cat "$TMP/vg.log" 2>/dev/null; exit 1; }
    tries=$((tries + 1))
    [ "$tries" -lt "$PORT_TRIES" ] || {
        echo "$tries candidate udp ports, up to $PORT, were all taken"; exit 1; }
    PORT=$((PORT + PORT_STEP))
    echo "udp port taken; retrying on $PORT"
done
echo "the daemon is up under valgrind on udp $PORT"
# One real command over QUIC — handshake, frames, teardown — with a
# timeout sized for valgrind's clock, then a clean stop so every exit
# path (and the allocator teardown) runs.
"$MUX" a run --quic "127.0.0.1:$PORT" --key "$KEY" --timeout 60000 "echo vg-probe" \
    || echo "mux a run failed under valgrind (timing?) — the leak summary below still stands"
"$MUX" d stop --sock "$SOCK" || kill "$VGPID" 2>/dev/null || true
wait "$VGPID" && VGRC=0 || VGRC=$?
VGPID=""
echo "valgrind exit: $VGRC (99 = valgrind-detected errors)"
echo "---- valgrind summary ----"
grep -E "definitely lost|indirectly lost|ERROR SUMMARY" "$TMP/vg.log" || cat "$TMP/vg.log"