v3-0001-Fix-safe_wal_size-for-slots-without-restart_lsn.patch

application/octet-stream

Filename: v3-0001-Fix-safe_wal_size-for-slots-without-restart_lsn.patch
Type: application/octet-stream
Part: 0
Message: Re: Fix safe_wal_size for slots without restart_lsn
From f12f74ed743319a3f271192bdc768d0e21324b41 Mon Sep 17 00:00:00 2001
From: alterego655 <824662526@qq.com>
Date: Wed, 27 May 2026 18:50:51 +0800
Subject: [PATCH v3] 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 | 31 ++++++++++++++++++++---
 3 files changed, 40 insertions(+), 10 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
        <structfield>safe_wal_size</structfield> <type>int8</type>
       </para>
       <para>
-       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 <varname>max_slot_wal_keep_size</varname>
-       is <literal>-1</literal>.
+       The number of bytes that can be written to WAL before this slot is
+       in danger of becoming <literal>lost</literal>.  It is
+       <literal>NULL</literal> for lost slots, for slots whose
+       <structfield>restart_lsn</structfield> is <literal>NULL</literal>,
+       and when <varname>max_slot_wal_keep_size</varname> is
+       <literal>-1</literal>.
       </para></entry>
      </row>
 
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..be0aaa32b85 100644
--- a/src/test/recovery/t/019_replslot_limit.pl
+++ b/src/test/recovery/t/019_replslot_limit.pl
@@ -22,16 +22,41 @@ 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',
+	qq[
+	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',
-	"SELECT restart_lsn IS NULL, wal_status is NULL, safe_wal_size is NULL FROM pg_replication_slots WHERE slot_name = 'rep1'"
+$result = $node_primary->safe_psql(
+	'postgres',
+	qq[
+	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