From 195f941600ad2cb194d5dd2d4d411b453fd3cfd5 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Mon, 2 Nov 2026 23:23:05 -0500 Subject: [PATCH 2/2] Add test for a failed bulk write inside a savepoint It uses a new injection point in smgr_bulk_finish(). --- src/backend/storage/smgr/bulk_write.c | 2 + src/test/modules/test_misc/meson.build | 1 + .../test_misc/t/016_bulk_write_savepoint.pl | 72 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 src/test/modules/test_misc/t/016_bulk_write_savepoint.pl diff --git a/src/backend/storage/smgr/bulk_write.c b/src/backend/storage/smgr/bulk_write.c index 8a9471d8715..f19ef13066f 100644 --- a/src/backend/storage/smgr/bulk_write.c +++ b/src/backend/storage/smgr/bulk_write.c @@ -41,6 +41,7 @@ #include "storage/bufpage.h" #include "storage/bulk_write.h" #include "storage/smgr.h" +#include "utils/injection_point.h" #include "utils/rel.h" #define MAX_PENDING_WRITES XLR_MAX_BLOCK_ID @@ -203,6 +204,7 @@ smgr_bulk_finish(BulkWriteState *bulkstate) * DELAY_CHKPT_START, which would stay set if smgrregistersync() * failed inside a subtransaction. */ + INJECTION_POINT("bulk-write-before-registersync", NULL); smgrregistersync(bulkstate->smgr, bulkstate->forknum); if (bulkstate->start_RedoRecPtr != GetRedoRecPtr()) diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build index 5d81f5b13be..6ac634ce810 100644 --- a/src/test/modules/test_misc/meson.build +++ b/src/test/modules/test_misc/meson.build @@ -24,6 +24,7 @@ tests += { 't/013_temp_obj_multisession.pl', 't/014_log_statement_max_length.pl', 't/015_temp_schema_exit_deferrable.pl', + 't/016_bulk_write_savepoint.pl', ], # The injection points are cluster-wide, so disable installcheck 'runningcheck': false, diff --git a/src/test/modules/test_misc/t/016_bulk_write_savepoint.pl b/src/test/modules/test_misc/t/016_bulk_write_savepoint.pl new file mode 100644 index 00000000000..de471ae3ee7 --- /dev/null +++ b/src/test/modules/test_misc/t/016_bulk_write_savepoint.pl @@ -0,0 +1,72 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Check that a bulk write that fails in smgr_bulk_finish(), with the +# error caught by a savepoint, does not make checkpoints wait until the +# top-level transaction ends. + +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'; +} + +# The injection point is on the path for WAL-logged bulk writes, so +# wal_level must be higher than minimal. +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init(allows_streaming => 1); +$node->start; + +if (!$node->check_extension('injection_points')) +{ + plan skip_all => 'Extension injection_points not installed'; +} + +$node->safe_psql( + 'postgres', q( + CREATE EXTENSION injection_points; + CREATE TABLE tab (a int); + INSERT INTO tab SELECT generate_series(1, 1000); +)); + +# Make CREATE INDEX fail in smgr_bulk_finish(), inside a savepoint. +my $psql = $node->background_psql('postgres', on_error_stop => 0); +$psql->query_safe( + q(SELECT injection_points_set_local(); + SELECT injection_points_attach('bulk-write-before-registersync', 'error'); + BEGIN; + SAVEPOINT sp;)); +$psql->query('CREATE INDEX tab_a_idx ON tab (a)'); +like( + $psql->{stderr}, + qr/error triggered for injection point bulk-write-before-registersync/, + 'CREATE INDEX fails in smgr_bulk_finish()'); +$psql->{stderr} = ''; +$psql->query_safe( + q(ROLLBACK TO sp; + SELECT injection_points_detach('bulk-write-before-registersync');)); + +# The transaction is still open. A checkpoint must not wait for it. +my $timed_out = 0; +$node->psql( + 'postgres', 'CHECKPOINT', + timeout => $PostgreSQL::Test::Utils::timeout_default, + timed_out => \$timed_out); +ok(!$timed_out, 'CHECKPOINT completes after ROLLBACK TO SAVEPOINT'); + +# Another bulk write in the same transaction works. +$psql->query_safe( + q(CREATE INDEX tab_a_idx ON tab (a); + COMMIT;)); +is( $node->safe_psql( + 'postgres', "SELECT count(*) FROM pg_index WHERE indexrelid = 'tab_a_idx'::regclass"), + '1', + 'second CREATE INDEX in the same transaction succeeds'); + +$psql->quit; +done_testing(); -- 2.37.1 (Apple Git-137.1)