From 2621f9a50a2d64e32db01e305c877ae7e5bbbe2c Mon Sep 17 00:00:00 2001
From: alterego655 <824662526@qq.com>
Date: Wed, 27 May 2026 18:50:51 +0800
Subject: [PATCH v2] Fix safe_wal_size for slots without restart_lsn
pg_replication_slots could report a non-NULL safe_wal_size for a
replication slot that had never reserved WAL, when max_slot_wal_keep_size
was finite. Such a slot has no restart_lsn, so WAL availability and
safe_wal_size are undefined.
Return NULL for safe_wal_size when WAL availability is invalid.
---
doc/src/sgml/system-views.sgml | 10 ++++++----
src/backend/replication/slotfuncs.c | 9 ++++++---
src/test/recovery/t/019_replslot_limit.pl | 21 +++++++++++++++++++--
3 files changed, 31 insertions(+), 9 deletions(-)
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 2ebec6928d5..39fa955ff89 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -3007,10 +3007,12 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
safe_wal_size int8
- The number of bytes that can be written to WAL such that this slot
- is not in danger of getting in state "lost". It is NULL for lost
- slots, as well as if max_slot_wal_keep_size
- is -1.
+ The number of bytes that can be written to WAL before this slot is
+ in danger of becoming lost. It is
+ NULL for lost slots, for slots whose
+ restart_lsn is NULL,
+ and when max_slot_wal_keep_size is
+ -1.
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 16fbd383735..44de95e6fa9 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -397,10 +397,13 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
}
/*
- * safe_wal_size is only computed for slots that have not been lost,
- * and only if there's a configured maximum size.
+ * safe_wal_size is only computed for slots with a valid restart_lsn
+ * that have not been lost, and only if there's a configured maximum
+ * size.
*/
- if (walstate == WALAVAIL_REMOVED || max_slot_wal_keep_size_mb < 0)
+ if (walstate == WALAVAIL_INVALID_LSN ||
+ walstate == WALAVAIL_REMOVED ||
+ max_slot_wal_keep_size_mb < 0)
nulls[i++] = true;
else
{
diff --git a/src/test/recovery/t/019_replslot_limit.pl b/src/test/recovery/t/019_replslot_limit.pl
index a412faf51c6..9d0f49ec694 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -22,16 +22,33 @@ max_wal_size = 4MB
log_checkpoints = yes
));
$node_primary->start;
+
+# A slot that has not reserved WAL has no meaningful WAL availability or
+# remaining safe WAL size, even when max_slot_wal_keep_size is finite.
+$node_primary->safe_psql('postgres',
+ "ALTER SYSTEM SET max_slot_wal_keep_size TO '6MB'; SELECT pg_reload_conf();"
+);
+$node_primary->safe_psql('postgres',
+ "SELECT pg_create_physical_replication_slot('rep_unreserved')");
+my $result = $node_primary->safe_psql('postgres',
+ "SELECT restart_lsn IS NULL, wal_status IS NULL, safe_wal_size IS NULL FROM pg_replication_slots WHERE slot_name = 'rep_unreserved'"
+);
+is($result, "t|t|t",
+ 'check non-reserved slot state with finite max_slot_wal_keep_size');
+$node_primary->safe_psql('postgres',
+ "SELECT pg_drop_replication_slot('rep_unreserved')");
+$node_primary->safe_psql('postgres',
+ "ALTER SYSTEM RESET max_slot_wal_keep_size; SELECT pg_reload_conf();");
+
$node_primary->safe_psql('postgres',
"SELECT pg_create_physical_replication_slot('rep1')");
# The slot state and remain should be null before the first connection
-my $result = $node_primary->safe_psql('postgres',
+$result = $node_primary->safe_psql('postgres',
"SELECT restart_lsn IS NULL, wal_status is NULL, safe_wal_size is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'"
);
is($result, "t|t|t", 'check the state of non-reserved slot is "unknown"');
-
# Take backup
my $backup_name = 'my_backup';
$node_primary->backup($backup_name);
--
2.51.0