From 16589a31788d7fc7c347f68c0e1dd55a57cccc14 Mon Sep 17 00:00:00 2001
From: shihao zhong <zhong950419@gmail.com>
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)

