# Copyright (c) 2026, PostgreSQL Global Development Group # # Negative controls of coverage-matrix.txt: DROP DATABASE (a barrier) with one control session # present: idle, pg_sleep(), a slow reader that keeps reading, a synchronous replication waiter # (no standby), and a paused COPY FROM STDIN. The session must survive; prints "CTRL ..." lines. # Run inside a PostgreSQL source tree (unpatched, + v6 or + v2): copy to src/test/recovery/t/ # without the .txt suffix, then: # make -C src/test/recovery check PROVE_TESTS=t/091_barrier_controls.pl PROVE_FLAGS=-v # Env: GUC (one extra postgresql.conf line, e.g. GUC='procsignal_barrier_timeout = 2s' for v2), # WINDOW (seconds, default 15), REPS (default 3). use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; use Test::More; use Time::HiRes qw(time usleep); my $WINDOW = $ENV{WINDOW} // 15; my $REPS = $ENV{REPS} // 3; my $node = PostgreSQL::Test::Cluster->new('c'); $node->init(allows_streaming => 1, no_data_checksums => 1); $node->append_conf('postgresql.conf', $ENV{GUC}) if $ENV{GUC}; # only the sync rep control session waits for a synchronous standby, not CREATE/DROP DATABASE $node->append_conf('postgresql.conf', 'synchronous_commit = local'); $node->start; $node->safe_psql('postgres', 'CREATE TABLE t (a int)'); my $n = 0; sub alive { $node->safe_psql('postgres', "SELECT count(*) FROM pg_stat_activity WHERE pid = $_[0]") eq '1'; } sub wpid { my ($app, $cond) = @_; $node->poll_query_until('postgres', "SELECT $cond FROM pg_stat_activity WHERE application_name = '$app'") or die "no $app"; $node->safe_psql('postgres', "SELECT pid FROM pg_stat_activity WHERE application_name = '$app'"); } # $tick->() runs while waiting (slow reader pump); $release->() after the window if still waiting sub run_case { my ($what, $pid, $tick, $release) = @_; my $db = 'x' . $n++; $node->safe_psql('postgres', "CREATE DATABASE $db"); my $off = -s $node->logfile; my ($o, $e) = ('', ''); my $op = IPC::Run::start( [ 'psql', '--no-psqlrc', '--quiet', '--dbname' => $node->connstr('postgres'), '--command' => "DROP DATABASE $db" ], '>' => \$o, '2>' => \$e); my $t0 = time; my $t; while (time - $t0 < $WINDOW) { $tick->(); $op->pump_nb if $op->pumpable; if (!$op->pumpable) { $t = time - $t0; last; } usleep(50_000); } my $al = alive($pid) ? 1 : 0; my $term = $node->log_contains("terminating process $pid ", $off) ? 1 : 0; my $bw = $node->log_contains("still waiting for backend with PID $pid to accept ProcSignalBarrier", $off) ? 1 : 0; my $after = '-'; if (!defined $t) { my $t1 = time; $release->(); while ($op->pumpable && time - $t1 < 60) { $op->pump_nb; usleep(5_000); } $after = sprintf('%.0fms', 1000 * (time - $t1)); $op->finish; } diag( sprintf( "CTRL %s: drop=%s survived=%d terminated=%d barrier_wait_logged=%d drop_after_release=%s", $what, defined $t ? sprintf('%.0fms', 1000 * $t) : "WAITING >${WINDOW}s", $al, $term, $bw, $after)); pass($what); } for my $r (1 .. $REPS) { my $idle = $node->background_psql('postgres'); my $ip = $idle->query_safe('SELECT pg_backend_pid()'); run_case('idle', $ip, sub { }, sub { }); $idle->quit; my $so = ''; my $sl = IPC::Run::start( [ 'psql', '--no-psqlrc', '--quiet', '--dbname' => $node->connstr('postgres') . " application_name=sl$r", '--command' => 'SELECT pg_sleep(600)' ], '>' => \$so, '2>' => \my $se); run_case('pg_sleep', wpid("sl$r", "wait_event = 'PgSleep'"), sub { }, sub { }); $sl->kill_kill; my $ro = ''; my $rd = IPC::Run::start( [ 'psql', '--no-psqlrc', '--quiet', '--dbname' => $node->connstr('postgres') . " application_name=rd$r", '--command' => "COPY (SELECT generate_series(1, 1000000000), repeat('x', 200)) TO STDOUT" ], '>' => \$ro, '2>' => \my $re); my $rp = wpid("rd$r", "wait_event = 'ClientWrite'"); run_case( 'slow reader', $rp, sub { $rd->pump_nb; $ro = ''; }, sub { $node->safe_psql('postgres', "SELECT pg_terminate_backend($rp)") }); $rd->kill_kill; $node->safe_psql('postgres', "ALTER SYSTEM SET synchronous_standby_names = 'nosuch'"); $node->reload; my $yo = ''; my $sy = IPC::Run::start( [ 'psql', '--no-psqlrc', '--quiet', '--dbname' => $node->connstr('postgres') . " application_name=sy$r", '--command' => 'SET synchronous_commit = on; INSERT INTO t VALUES (1)' ], '>' => \$yo, '2>' => \my $ye); run_case( 'sync-rep waiter', wpid("sy$r", "wait_event = 'SyncRep'"), sub { }, sub { $node->safe_psql('postgres', 'ALTER SYSTEM RESET synchronous_standby_names'); $node->reload; }); $sy->finish; my ($ci, $co) = ("COPY t FROM STDIN;\n1\n", ''); my $cp = IPC::Run::start( [ 'psql', '--no-psqlrc', '--quiet', '--dbname' => $node->connstr('postgres') . " application_name=cp$r" ], '<' => \$ci, '>' => \$co, '2>' => \my $ce); $cp->pump_nb; run_case( 'COPY FROM STDIN paused', wpid("cp$r", "wait_event = 'ClientRead' AND state = 'active'"), sub { }, sub { $ci .= "2\n\\.\n"; $cp->pump_nb for 1 .. 5; }); $cp->finish; } done_testing();