Greetings,

recovery/t/020_archive_status.pl checks archiving progress in two separate
queries: it polls pg_stat_archiver for one condition, then asserts a related
one.  For the primary it polls until archived_count is exactly 1, then
separately asserts last_archived_wal equals a specific segment; for the
always-on standby it polls until last_archived_wal equals a segment, then
asserts archived_count is exactly 2.

Both are racy.  A checkpoint or crash-recovery segment archived between the
two queries pushes the count or last_archived_wal past the exact value
expected.  The exact-equality poll is fragile on its own, too: if the count
jumps past the target, the poll never sees it and times out.

The fix uses monotonic >= instead of exact equality, so the checks tolerate
extra archived segments.  WAL segment names sort lexically, so >= is
well defined for last_archived_wal.

--
Bryan Green
EDB: https://www.enterprisedb.com
From 4cebee5b894c83eb3e1649e0a277830d9c4fe22d Mon Sep 17 00:00:00 2001
From: Bryan Green <[email protected]>
Date: Sat, 8 Aug 2026 15:37:22 -0500
Subject: [PATCH] Fix TOCTOU races in recovery/t/020_archive_status.pl archive
 checks

The test polled pg_stat_archiver for one condition (archived_count, or
last_archived_wal) and then, in a separate query, asserted a related
condition.  Between the two queries additional WAL segments can be
archived (checkpoint or crash-recovery activity), so the second query can
observe values that no longer match the exact ones expected; the poll's
exact-equality check can also miss a value entirely if the count jumps
past it.

Use monotonic >= comparisons so the checks tolerate additional archived
segments.  WAL segment names are lexically ordered, so >= is well defined
for last_archived_wal.

Co-authored-by: Mark Dilger <[email protected]>
---
 src/test/recovery/t/020_archive_status.pl | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/src/test/recovery/t/020_archive_status.pl 
b/src/test/recovery/t/020_archive_status.pl
index 5bb8aa9ec1..abfebb709f 100644
--- a/src/test/recovery/t/020_archive_status.pl
+++ b/src/test/recovery/t/020_archive_status.pl
@@ -93,8 +93,12 @@ $primary->safe_psql(
        SELECT pg_reload_conf();
 });
 
+# Wait for the .done file to appear, which is the definitive indicator
+# that this specific segment was archived.  Polling pg_stat_archiver for
+# a specific last_archived_wal is unreliable because a later segment may
+# be archived first (or additionally), changing last_archived_wal.
 $primary->poll_query_until('postgres',
-       q{SELECT archived_count FROM pg_stat_archiver}, '1')
+       q{SELECT archived_count >= 1 FROM pg_stat_archiver}, 't')
   or die "Timed out while waiting for archiving to finish";
 
 ok(!-f "$primary_data/$segment_path_1_ready",
@@ -104,9 +108,9 @@ ok(-f "$primary_data/$segment_path_1_done",
        ".done file for archived WAL segment $segment_name_1 exists");
 
 is( $primary->safe_psql(
-               'postgres', q{ SELECT last_archived_wal FROM pg_stat_archiver 
}),
-       $segment_name_1,
-       "archive success reported in pg_stat_archiver for WAL segment 
$segment_name_1"
+               'postgres', q{ SELECT last_archived_wal >= } . 
qq{'$segment_name_1' FROM pg_stat_archiver }),
+       't',
+       "archive success reported in pg_stat_archiver for WAL segment >= 
$segment_name_1"
 );
 
 # Create some WAL activity and a new checkpoint so as the next standby can
@@ -214,14 +218,16 @@ $standby2->safe_psql(
        ALTER SYSTEM RESET archive_command;
        SELECT pg_reload_conf();
 });
+# Wait for at least 2 segments to be archived.  Don't require an exact
+# count or a specific last_archived_wal: additional segments from crash
+# recovery or checkpoint activity are legitimate.
 $standby2->poll_query_until('postgres',
-       q{SELECT last_archived_wal FROM pg_stat_archiver},
-       $segment_name_2)
+       q{SELECT archived_count >= 2 FROM pg_stat_archiver}, 't')
   or die "Timed out while waiting for archiving to finish";
 
 is( $standby2->safe_psql(
-               'postgres', q{SELECT archived_count FROM pg_stat_archiver}),
-       '2',
+               'postgres', q{SELECT archived_count >= 2 FROM 
pg_stat_archiver}),
+       't',
        "correct number of WAL segments archived from standby");
 
 ok( !-f "$standby2_data/$segment_path_1_ready"
-- 
2.49.0

Reply via email to