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