0001-add-regression-test.txt
text/plain
Filename: 0001-add-regression-test.txt
Type: text/plain
Part: 0
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