# Copyright (c) 2026, PostgreSQL Global Development Group # # Rows 6-8 of coverage-matrix.txt: barrier waits on a primary behind a session blocked in # ClientWrite (pg_enable_data_checksums(), DROP DATABASE with the session on another database, # DROP DATABASE with a walsender stuck in a base backup). Prints one "ROW ..." line per case. # 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/090_barrier_primary_rows.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 $copy = "COPY (SELECT generate_series(1, 1000000000), repeat('x', 200)) TO STDOUT"; my $node = PostgreSQL::Test::Cluster->new('p'); $node->init(allows_streaming => 1, no_data_checksums => 1); $node->append_conf('postgresql.conf', $ENV{GUC}) if $ENV{GUC}; $node->start; $node->safe_psql('postgres', 'CREATE DATABASE other'); sub psql_bg { my ($db, $app, $sql, $out) = @_; IPC::Run::start( [ 'psql', '--no-psqlrc', '--quiet', '--dbname' => $node->connstr($db) . " application_name=$app", '--command' => $sql ], '>' => $out, '2>' => \my $e); } sub pid_of { my ($app) = @_; $node->poll_query_until('postgres', "SELECT wait_event = 'ClientWrite' 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'"); } # Wake the session until it stays blocked: a woken backend can still fit one more send on a # Unix-domain socket, reach CHECK_FOR_INTERRUPTS() and absorb a pending barrier. sub settle { my ($pid) = @_; for my $i (0 .. 20) { my $o = -s $node->logfile; $node->safe_psql('postgres', "SELECT pg_log_backend_memory_contexts($pid)"); usleep(500_000); return unless $node->log_contains("logging memory contexts of PID $pid", $o); $node->poll_query_until('postgres', "SELECT wait_event = 'ClientWrite' FROM pg_stat_activity WHERE pid = $pid"); } die "never settled"; } sub judge { my ($what, $pid, $done, $off) = @_; my $t0 = time; my $t; while (time - $t0 < $WINDOW) { if ($done->()) { $t = time - $t0; last; } usleep(20_000); } my $term = $node->log_contains("terminating process $pid ", $off) ? 1 : 0; my $rel = '-'; if (!defined $t) { $node->safe_psql('postgres', "SELECT pg_terminate_backend($pid)"); my $t1 = time; usleep(5_000) until $done->() || time - $t1 > 60; $rel = sprintf('%.0fms', 1000 * (time - $t1)); } diag( sprintf( "ROW %s: %s terminated_by_timeout=%d manual_release=%s", $what, defined $t ? sprintf('CLEARED %.0fms', 1000 * $t) : "HUNG >${WINDOW}s", $term, $rel)); pass($what); } for my $r (1 .. $REPS) { $node->safe_psql('postgres', "CREATE DATABASE d$r; CREATE DATABASE w$r"); my $o = ''; my $h = psql_bg('other', "s_drop$r", $copy, \$o); my $pid = pid_of("s_drop$r"); settle($pid); my $off = -s $node->logfile; my $oo = ''; my $op = psql_bg('postgres', 'op', "DROP DATABASE d$r", \$oo); judge( 'DROP DATABASE (session on another db)', $pid, sub { $op->pump_nb if $op->pumpable; !$op->pumpable }, $off); $h->kill_kill; $o = ''; $h = psql_bg('postgres', "s_cs$r", $copy, \$o); $pid = pid_of("s_cs$r"); settle($pid); $off = -s $node->logfile; $node->safe_psql('postgres', 'SELECT pg_enable_data_checksums()'); judge( 'pg_enable_data_checksums()', $pid, sub { $node->safe_psql('postgres', "SELECT setting FROM pg_settings WHERE name = 'data_checksums'") eq 'on'; }, $off); $h->kill_kill; $node->safe_psql('postgres', 'SELECT pg_disable_data_checksums()'); $node->poll_query_until('postgres', "SELECT setting = 'off' FROM pg_settings WHERE name = 'data_checksums'") or die; my ($bo, $be) = ('', ''); $h = IPC::Run::start( [ 'pg_basebackup', '--pgdata' => '-', '--format' => 'tar', '--wal-method' => 'none', '--checkpoint' => 'fast', '--dbname' => $node->connstr('postgres') . " application_name=s_ws$r" ], '>' => \$bo, '2>' => \$be); $pid = pid_of("s_ws$r"); settle($pid); $off = -s $node->logfile; $oo = ''; $op = psql_bg('postgres', 'op', "DROP DATABASE w$r", \$oo); judge( 'DROP DATABASE (walsender in a base backup)', $pid, sub { $op->pump_nb if $op->pumpable; !$op->pumpable }, $off); $h->kill_kill; $node->poll_query_until('postgres', "SELECT count(*) = 0 FROM pg_stat_activity WHERE application_name LIKE 's\\_%'"); } done_testing();