0001-add-regression-test.txt

text/plain

Filename: 0001-add-regression-test.txt
Type: text/plain
Part: 0
Message: RE: Logical replication 'invalid memory alloc request size 1585837200' after upgrading to 17.5
From b5df911769d7c56c95ece58da22267677b23d66a Mon Sep 17 00:00:00 2001
From: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Date: Wed, 4 Jun 2025 15:42:09 +0900
Subject: [PATCH] add regression test

---
 contrib/test_decoding/Makefile                |  4 +
 contrib/test_decoding/meson.build             |  4 +
 contrib/test_decoding/t/002_inval.pl          | 88 +++++++++++++++++++
 .../replication/logical/reorderbuffer.c       | 15 +++-
 4 files changed, 110 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/t/002_inval.pl

diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile
index 02e961f4d31..a93a3fa78cb 100644
--- a/contrib/test_decoding/Makefile
+++ b/contrib/test_decoding/Makefile
@@ -20,6 +20,10 @@ NO_INSTALLCHECK = 1
 
 TAP_TESTS = 1
 
+EXTRA_INSTALL=src/test/modules/injection_points
+
+export enable_injection_points
+
 ifdef USE_PGXS
 PG_CONFIG = pg_config
 PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build
index 25f6b8a9082..a1f2498ff1c 100644
--- a/contrib/test_decoding/meson.build
+++ b/contrib/test_decoding/meson.build
@@ -72,8 +72,12 @@ tests += {
     'runningcheck': false,
   },
   'tap': {
+    'env': {
+      'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
+    },
     'tests': [
       't/001_repl_stats.pl',
+      't/002_inval.pl',
     ],
   },
 }
diff --git a/contrib/test_decoding/t/002_inval.pl b/contrib/test_decoding/t/002_inval.pl
new file mode 100644
index 00000000000..0558b917b7a
--- /dev/null
+++ b/contrib/test_decoding/t/002_inval.pl
@@ -0,0 +1,88 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Tests for various bugs found over time
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('publisher');
+$node->init(allows_streaming => 'logical');
+$node->start;
+
+# Check if the extension injection_points is available, as it may be
+# possible that this script is run with installcheck, where the module
+# would not be installed by default.
+if (!$node->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+# Define database objects
+$node->safe_psql("postgres", "CREATE TABLE tab (id int);");
+$node->safe_psql("postgres", "SELECT pg_create_logical_replication_slot('test', 'test_decoding');");
+
+# Bump the query timeout to avoid false negatives on slow test systems.
+my $psql_timeout_secs = 4 * $PostgreSQL::Test::Utils::timeout_default;
+
+my @sessions = ();
+my $num_sessions = 4;
+
+# Start background sessions and push to the array once
+for my $i (1..$num_sessions)
+{
+    push(@sessions, $node->background_psql('postgres',
+                                timeout => $psql_timeout_secs));
+}
+
+# Start transactions on sessions. Each transactions have invalidation messages
+for my $i (0.. $#sessions)
+{
+    $sessions[$i]->query_safe(
+	qq[
+	BEGIN;
+    INSERT INTO tab VALUES (1);
+    CREATE TABLE tmp_$i (id int);
+    DROP TABLE tmp_$i;
+    ]);
+}
+
+# Commit all transactions and exit
+foreach my $session (@sessions)
+{
+    $session->query_safe(
+	qq[
+    COMMIT;
+    ]);
+    $session->quit;
+}
+
+# Confirms all transactions were committed
+is($node->safe_psql('postgres', "SELECT count(*) FROM tab;"), $num_sessions);
+
+# Decodes changes and confirms whether all caches were invalidated due to the
+# cache overflow.
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points');
+$node->safe_psql('postgres',
+	"SELECT injection_points_attach('inval-message-overflow', 'error');");
+
+# Increase the log level and set offset because the evidence is output as DEBUG1.
+$node->append_conf('postgresql.conf', 'log_min_messages = debug1');
+$node->reload();
+my $log_location = -s $node->logfile;
+
+# Decodes changes from parallel transactions
+$node->safe_psql(
+	'postgres', "SELECT data FROM pg_logical_slot_get_binary_changes('test', NULL, NULL);"
+);
+
+# Confirms the debug log has been output
+$node->wait_for_log(qr/RBTXN_INVAL_OVERFLOWED flag was set to the transaction/, $log_location);
+
+done_testing();
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index e05259bf693..e189e5b9e6f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -109,6 +109,7 @@
 #include "storage/procarray.h"
 #include "storage/sinval.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -3578,6 +3579,7 @@ ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid,
 {
 	ReorderBufferTXN *txn;
 	MemoryContext oldcontext;
+	Size		  maximum;
 
 	txn = ReorderBufferTXNByXid(rb, xid, true, NULL, lsn, true);
 
@@ -3592,11 +3594,20 @@ ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid,
 
 	Assert(nmsgs > 0);
 
+#ifdef USE_INJECTION_POINTS
+	if (IS_INJECTION_POINT_ATTACHED("inval-message-overflow"))
+		maximum = 100;
+	else
+		maximum = MAX_DISTR_INVAL_MSG_PER_TXN;
+#else
+	maximum = MAX_DISTR_INVAL_MSG_PER_TXN;
+#endif
+
 	/*
 	 * Check the transaction has enough space for storing distributed
 	 * invalidation messages.
 	 */
-	if (txn->ninvalidations_distributed + nmsgs >= MAX_DISTR_INVAL_MSG_PER_TXN)
+	if (txn->ninvalidations_distributed + nmsgs >= maximum)
 	{
 		/*
 		 * Mark the invalidation message as overflowed and free up the
@@ -3604,6 +3615,8 @@ ReorderBufferAddDistributedInvalidations(ReorderBuffer *rb, TransactionId xid,
 		 */
 		txn->txn_flags |= RBTXN_INVAL_OVERFLOWED;
 
+		elog(DEBUG1, "RBTXN_INVAL_OVERFLOWED flag was set to the transaction");
+
 		if (txn->invalidations_distributed)
 		{
 			pfree(txn->invalidations_distributed);
-- 
2.47.1