#!/usr/bin/env bash # #6957 v03-0008: WHO assigns the XID to the REPACK backend before the replay? # # The Assert in compute_new_xmax_infomask() (heapam.c) requires that, if the # xmax about to be written is not ours, we must not have a top XID: # # Assert(TransactionIdIsCurrentTransactionId(add_to_xmax) || # !TransactionIdIsValid(GetTopTransactionIdIfAny())) # # and it fails during the replay that runs after AccessExclusiveLock is taken. # Antonin said (2026-09-23) he does not know when that XID gets assigned. This # measures it instead of reasoning about it: gdb attaches TO THE REPACK BACKEND # and dumps a backtrace on every AssignTransactionId(), plus the one at the # TRAP. The last backtrace before the TRAP is the one that assigned the XID # that breaks the Assert. # # catch_xid.sh [build] [repack_snapshot_after] set -u N=${1:-p6957a} AFTER=${2:-50} BIN=$HOME/pgprog/i-$N/bin HERE=$(cd "$(dirname "$0")" && pwd) PGD=$(mktemp -d /tmp/claude-1000/catchxid.XXXX) PORT=54967 OUT=$HERE/cazar_xid.out export PGPORT=$PORT PGHOST=/tmp PGDATABASE=postgres "$BIN/initdb" -D "$PGD" -A trust --no-sync > /dev/null cat >> "$PGD/postgresql.conf" < /dev/null P="$BIN/psql -X -q" $P -c "CREATE TABLE t (id int PRIMARY KEY, v text)" \ -c "INSERT INTO t SELECT g, repeat('x', 200) FROM generate_series(1, 30000) g" # The session that runs REPACK: through a fifo, so we have its PID beforehand. FIFO=$(mktemp -u); mkfifo "$FIFO" # -At (unaligned, no headers) is required: with the table format, \g also # writes the "(1 row)" line and the PID comes out with a 1 glued to it. "$BIN/psql" -X -q -At < "$FIFO" > "$PGD/repack.out" 2>&1 & exec 3> "$FIFO" echo "SELECT pg_backend_pid() \\g $PGD/pid" >&3 for _ in $(seq 40); do [ -s "$PGD/pid" ] && break; sleep 0.2; done PID=$(tr -dc '0-9' < "$PGD/pid") kill -0 "$PID" 2>/dev/null || { echo "invalid PID: '$PID'"; exit 1; } echo "== REPACK backend: $PID" cat > "$PGD/catch.gdb" <<'EOF' set pagination off set confirm off set print frame-arguments all # Postgres uses SIGUSR1 for IPC all the time: without this, gdb stops at the # first signal, the trailing `continue` is consumed there, and not a single # AssignTransactionId is captured (seen: 0 captures and one backtrace sitting # in epoll_wait). handle SIGUSR1 nostop noprint pass handle SIGUSR2 nostop noprint pass handle SIGALRM nostop noprint pass handle SIGPIPE nostop noprint pass break AssignTransactionId commands silent printf "\n=== AssignTransactionId ===\n" bt 22 continue end continue printf "\n=== THE BACKEND STOPPED (TRAP/signal) ===\n" bt 30 EOF gdb -p "$PID" -batch -x "$PGD/catch.gdb" > "$OUT" 2>&1 & GDB=$! sleep 3 # let gdb finish attaching before sending anything # Concurrent load: with no UPDATEs to replay there is no replay to break. ( for i in $(seq 1 300); do $P -c "UPDATE t SET v = 'upd' WHERE id = $i * 7" 2> /dev/null done ) & LOAD=$! [ "$AFTER" = 0 ] || echo "SET repack_snapshot_after = $AFTER;" >&3 echo "REPACK (CONCURRENTLY) t;" >&3 echo "SELECT 'done' \\g $PGD/done" >&3 for _ in $(seq 180); do [ -s "$PGD/done" ] && break; kill -0 "$PID" 2>/dev/null || break; sleep 1; done wait "$LOAD" 2>/dev/null exec 3>&- ; rm -f "$FIFO" wait "$GDB" 2>/dev/null echo "== TRAPs in the log: $(grep -c '^TRAP' "$PGD/server.log")" grep -m1 '^TRAP' "$PGD/server.log" | cut -c1-150 echo "== XID assignments captured: $(grep -c '=== AssignTransactionId ===' "$OUT")" cp "$PGD/server.log" "$HERE/cazar_xid.server.log" cp "$PGD/repack.out" "$HERE/cazar_xid.repack.out" 2>/dev/null "$BIN/pg_ctl" -D "$PGD" -m immediate -w stop > /dev/null 2>&1 rm -rf "$PGD" echo "== backtraces in $OUT"