summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFujii Masao2026-09-03 03:14:44 +0000
committerFujii Masao2026-09-03 03:15:17 +0000
commitd2943b94bcff19de5be3e84e2803a22010693f70 (patch)
tree58ae8a3947f2a1ea59ae20967bf9c6ed557e5432
parent8254de34bc4226756755ee585c0e24dd995e12a2 (diff)
Avoid backend hang during temp table cleanup in deferrable transactions
Previously, a session that had created temporary objects could hang during exit if its default transaction mode was SERIALIZABLE READ ONLY DEFERRABLE and a concurrent serializable transaction was prepared. During backend exit, temporary-relation cleanup pushed a regular transaction snapshot, which honored the session's default settings and could wait for a safe serializable snapshot. So, if the conflicting transaction was prepared, this wait could last indefinitely. The wait occurred while the backend was already exiting and interrupts were held off, so even pg_terminate_backend() could not cancel it. The backend therefore remained until the prepared transaction was resolved. Temporary-relation cleanup only needs an active MVCC snapshot for fetching TOAST data from catalog tuples while dropping temporary objects. It does not need a transaction snapshot affected by the user's default isolation settings. So, use a catalog snapshot instead, which provides the needed protection without entering the deferrable safe-snapshot wait. Backpatch to all supported versions. Bug: #19441 Reported-by: Alexander Lakhin <exclusion@gmail.com> Author: Andrey Rachitskiy <pl0h0yp1@gmail.com> Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru> Reviewed-by: Fujii Masao <masao.fujii@gmail.com> Discussion: https://postgr.es/m/19441-ec29f3b1363b4a68@postgresql.org Backpatch-through: 14
-rw-r--r--src/backend/catalog/namespace.c10
-rw-r--r--src/test/modules/test_misc/meson.build1
-rw-r--r--src/test/modules/test_misc/t/015_temp_schema_exit_deferrable.pl58
3 files changed, 68 insertions, 1 deletions
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index a0e4ce24a7c..8e0c02d2dd5 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -4631,7 +4631,15 @@ RemoveTempRelationsCallback(int code, Datum arg)
/* Need to ensure we have a usable transaction. */
AbortOutOfAnyTransaction();
StartTransactionCommand();
- PushActiveSnapshot(GetTransactionSnapshot());
+
+ /*
+ * Need an active snapshot for toast fetches during deletion. Do not
+ * use GetTransactionSnapshot(): under SERIALIZABLE READ ONLY
+ * DEFERRABLE it may wait in GetSafeSnapshot(), and proc_exit holds
+ * off interrupts so that wait cannot be cancelled. A catalog
+ * snapshot is enough and avoids that path.
+ */
+ PushActiveSnapshot(GetCatalogSnapshot(RelationRelationId));
RemoveTempRelations(myTempNamespace);
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 216c38016ae..23939fab2a8 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -18,6 +18,7 @@ tests += {
't/007_catcache_inval.pl',
't/008_replslot_single_user.pl',
't/013_temp_obj_multisession.pl',
+ 't/015_temp_schema_exit_deferrable.pl',
],
},
}
diff --git a/src/test/modules/test_misc/t/015_temp_schema_exit_deferrable.pl b/src/test/modules/test_misc/t/015_temp_schema_exit_deferrable.pl
new file mode 100644
index 00000000000..07de36630a6
--- /dev/null
+++ b/src/test/modules/test_misc/t/015_temp_schema_exit_deferrable.pl
@@ -0,0 +1,58 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Backend exit must finish temp-schema cleanup even when the session default
+# is SERIALIZABLE READ ONLY DEFERRABLE and a prepared serializable transaction
+# is still around.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('deferrable_temp_exit');
+$node->init;
+$node->append_conf('postgresql.conf', 'max_prepared_transactions = 1');
+$node->start;
+
+my $psql1 = $node->background_psql('postgres');
+
+$psql1->query_safe(
+ q{
+CREATE TEMPORARY TABLE tt (i int);
+SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;
+});
+
+my $pid = $psql1->query_safe(q{SELECT pg_backend_pid();});
+chomp $pid;
+like($pid, qr/^\d+$/, "backend pid $pid");
+
+# Overlapping read/write serializable xact that outlives session 1.
+$node->safe_psql(
+ 'postgres',
+ q{
+CREATE TABLE t (i int);
+BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+INSERT INTO t VALUES (1);
+PREPARE TRANSACTION 'pt';
+});
+
+# Disconnect: RemoveTempRelationsCallback runs during backend exit.
+$psql1->quit;
+
+ok( $node->poll_query_until(
+ 'postgres',
+ "SELECT count(*) = 0 FROM pg_stat_activity WHERE pid = $pid"),
+ 'backend exited despite prepared serializable xact');
+
+is( $node->safe_psql(
+ 'postgres',
+ q{SELECT count(*) FROM pg_class WHERE relname = 'tt' AND relpersistence = 't'}
+ ),
+ '0',
+ 'temporary table cleaned up on exit');
+
+$node->safe_psql('postgres', q{ROLLBACK PREPARED 'pt';});
+$node->safe_psql('postgres', q{DROP TABLE t;});
+
+done_testing();