#!/usr/bin/env bash # What pg_stat_progress_create_index shows in REINDEX CONCURRENTLY's wait # for readers when the wait before it, for old snapshots, waited on two or # more transactions (it may also count a session that was polling the view # when it took its list). WaitForOlderSnapshots() leaves lockers_done at # that wait's count, and the wait for readers only sets lockers_total, so # the view shows lockers_done greater than lockers_total. Every step waits # for the view to show the state it needs. Any build; on a PROGRESS_DEBUG # build the trace is on and its WAITFOR lines are printed too. # repro-waitfor.sh set -euo pipefail BIN=$1 W=$2; rm -rf "$W"; mkdir -p "$W" export LD_LIBRARY_PATH=$BIN/../lib PGDATA=$W/data; PORT=$((20000 + RANDOM % 10000)) export PGHOST=$W PGPORT=$PORT PGDATABASE=postgres cleanup() { exec 3>&- 4>&- 5>&- 2>/dev/null || true "$BIN/pg_ctl" -D "$PGDATA" -m immediate -w stop >/dev/null 2>&1 || true } trap cleanup EXIT "$BIN/initdb" -A trust -N -D "$PGDATA" >/dev/null TR=; "$BIN/postgres" -D "$PGDATA" -C trace_progress >/dev/null 2>&1 && TR='-c trace_progress=on' "$BIN/pg_ctl" -D "$PGDATA" -o "-p $PORT -k $W -c autovacuum=off $TR" -l "$W/log" -w start >/dev/null q() { "$BIN/psql" -X -At -c "$1"; } until_true() { for _ in $(seq 300); do [ "$(q "$1")" = t ] && return 0; sleep 0.1; done echo "timed out waiting for: $1" >&2; exit 1 } q "CREATE TABLE t (a int); CREATE INDEX t_a ON t (a); INSERT INTO t SELECT generate_series(1, 1000); CREATE TABLE u (a int)" >/dev/null for s in 1 2 3; do mkfifo "$W/s$s"; "$BIN/psql" -X -At <"$W/s$s" >"$W/s$s.out" 2>&1 & done exec 3>"$W/s1" 4>"$W/s2" 5>"$W/s3" # two old snapshots, no lock on t for fd in 3 4; do echo "BEGIN ISOLATION LEVEL REPEATABLE READ; SELECT count(*) FROM u;" >&$fd done until_true "SELECT count(*) = 2 FROM pg_stat_activity WHERE state = 'idle in transaction' AND backend_xmin IS NOT NULL" "$BIN/psql" -X -q -c 'REINDEX TABLE CONCURRENTLY t' & R=$! until_true "SELECT coalesce(bool_or(phase = 'waiting for old snapshots' AND lockers_total >= 2), false) FROM pg_stat_progress_create_index" echo "--- during the wait for old snapshots:" q "SELECT 'lockers_total=' || lockers_total FROM pg_stat_progress_create_index" # a reader of t, which the wait for readers will wait on echo "BEGIN; SELECT count(*) FROM t;" >&5 until_true "SELECT count(*) = 1 FROM pg_locks l JOIN pg_stat_activity a USING (pid) WHERE l.relation = 't'::regclass AND a.state = 'idle in transaction'" # end both old snapshots echo "COMMIT;" >&3 echo "COMMIT;" >&4 until_true "SELECT coalesce(bool_or(phase = 'waiting for readers before marking dead'), false) FROM pg_stat_progress_create_index" echo "--- during the wait for readers:" q "SELECT 'phase=' || phase || ' lockers_total=' || lockers_total || ' lockers_done=' || lockers_done FROM pg_stat_progress_create_index" echo "COMMIT;" >&5 wait $R exec 3>&- 4>&- 5>&-; wait "$BIN/pg_ctl" -D "$PGDATA" -w stop >/dev/null if [ -n "$TR" ]; then echo "--- trace: the WAITFOR parameters (3 total, 4 done, 5 pid) and phases (0):" grep -E 'LOG: progress (start|end|update|incr|multi).*CREATE_INDEX' "$W/log" | sed -E 's/^.*LOG: progress //' | grep -E '(^| )(0|3|4|5):|start|end' | sed 's/^/ /' fi