From b5df911769d7c56c95ece58da22267677b23d66a Mon Sep 17 00:00:00 2001 From: Hayato Kuroda 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