From 16589a31788d7fc7c347f68c0e1dd55a57cccc14 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Wed, 14 Oct 2026 08:22:27 -0400 Subject: [PATCH v2 2/2] Test barrier absorption with a stuck client Promote a standby while a backend there is blocked writing to a client that stopped reading, and drop a database while a backend is blocked reading COPY data that never comes. --- src/test/recovery/meson.build | 1 + .../recovery/t/060_barrier_blocked_client.pl | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/test/recovery/t/060_barrier_blocked_client.pl diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index e1cf57ff611..996ab12620e 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -68,6 +68,7 @@ tests += { 't/057_snapshot_commit_race.pl', 't/058_shutdown_crash_restart.pl', 't/059_remote_apply_status_interval.pl', + 't/060_barrier_blocked_client.pl', ], }, } diff --git a/src/test/recovery/t/060_barrier_blocked_client.pl b/src/test/recovery/t/060_barrier_blocked_client.pl new file mode 100644 index 00000000000..11ec9a00b7e --- /dev/null +++ b/src/test/recovery/t/060_barrier_blocked_client.pl @@ -0,0 +1,80 @@ + +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Check that a backend blocked on a client write or read still absorbs +# ProcSignalBarriers, so that a stuck client cannot hold up promotion or +# DROP DATABASE. + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $primary = PostgreSQL::Test::Cluster->new('primary'); +$primary->init(allows_streaming => 1); +$primary->start; +$primary->safe_psql('postgres', + 'CREATE TABLE copy_target (a int); CREATE DATABASE dropme;'); +$primary->backup('backup'); + +my $standby = PostgreSQL::Test::Cluster->new('standby'); +$standby->init_from_backup($primary, 'backup', has_streaming => 1); +$standby->start; + +# A client that stops reading. psql fetches the result in chunks and blocks +# writing to a pipe that we never read, so the backend fills its socket +# buffer and blocks in ClientWrite. +my ($in, $out, $err) = ('', '', ''); +my $writer = IPC::Run::start( + [ + 'psql', '--no-psqlrc', + '--set' => 'FETCH_COUNT=100', + '--dbname' => $standby->connstr('postgres'), + '--command' => + "SELECT repeat('x', 1000) FROM generate_series(1, 1000000)" + ], + '<' => \$in, + '>' => \$out, + '2>' => \$err); + +$standby->poll_query_until('postgres', + "SELECT count(*) = 1 FROM pg_stat_activity WHERE wait_event = 'ClientWrite'" +) or die "timed out waiting for a backend to block in ClientWrite"; + +# Promotion waits for every process to absorb a barrier. +my $promoted = $standby->safe_psql('postgres', + "SELECT pg_promote(true, $PostgreSQL::Test::Utils::timeout_default)"); +is($promoted, 't', 'promotion completes with a backend blocked on a write'); + +$writer->kill_kill; + +# A client that stops sending. COPY FROM STDIN waits for data on psql's +# stdin, which we never write to, so the backend blocks in ClientRead. +my ($in2, $out2, $err2) = ('', '', ''); +my $reader = IPC::Run::start( + [ + 'psql', '--no-psqlrc', + '--dbname' => $primary->connstr('postgres'), + '--command' => 'COPY copy_target FROM STDIN' + ], + '<' => \$in2, + '>' => \$out2, + '2>' => \$err2); + +$primary->poll_query_until('postgres', + "SELECT count(*) = 1 FROM pg_stat_activity WHERE wait_event = 'ClientRead' AND state = 'active'" +) or die "timed out waiting for a backend to block in ClientRead"; + +# DROP DATABASE waits for every process to absorb a barrier. +my $timed_out = 0; +my $ret = $primary->psql( + 'postgres', 'DROP DATABASE dropme', + timeout => $PostgreSQL::Test::Utils::timeout_default, + timed_out => \$timed_out); +is($ret, 0, 'DROP DATABASE completes with a backend blocked on a read'); + +$reader->kill_kill; + +done_testing(); -- 2.37.1 (Apple Git-137.1)