From fb5d5dc9e1e8fc395752906490e4d18d645e5994 Mon Sep 17 00:00:00 2001 From: Mikhail Nikalayeu Date: Tue, 4 Aug 2026 00:46:42 +0200 Subject: [PATCH v1] Do not hold rd_smgr across the fork loop when enabling data checksums ProcessSingleRelationByOid() called RelationGetSmgr(rel), discarded the result, and then read rel->rd_smgr directly on each iteration of the loop over forks. Only RelationGetSmgr() is authorized to read that field because a relcache invalidation resets rd_smgr to NULL. When one lands between two forks of the same relation, the next iteration passes NULL to smgrexists() and the worker dies. --- src/backend/postmaster/datachecksum_state.c | 3 +- src/test/modules/test_checksums/meson.build | 1 + .../test_checksums/t/010_discard_caches.pl | 49 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/test/modules/test_checksums/t/010_discard_caches.pl diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c index 569399fb2b1..be308a282d5 100644 --- a/src/backend/postmaster/datachecksum_state.c +++ b/src/backend/postmaster/datachecksum_state.c @@ -841,11 +841,10 @@ ProcessSingleRelationByOid(Oid relationId, BufferAccessStrategy strategy) pgstat_report_activity(STATE_IDLE, NULL); return true; } - RelationGetSmgr(rel); for (ForkNumber fnum = 0; fnum <= MAX_FORKNUM; fnum++) { - if (smgrexists(rel->rd_smgr, fnum)) + if (smgrexists(RelationGetSmgr(rel), fnum)) { if (!ProcessSingleRelationFork(rel, fnum, strategy)) { diff --git a/src/test/modules/test_checksums/meson.build b/src/test/modules/test_checksums/meson.build index 9b1421a9b91..c19e84210a2 100644 --- a/src/test/modules/test_checksums/meson.build +++ b/src/test/modules/test_checksums/meson.build @@ -33,6 +33,7 @@ tests += { 't/007_pgbench_standby.pl', 't/008_pitr.pl', 't/009_fpi.pl', + 't/010_discard_caches.pl', ], }, } diff --git a/src/test/modules/test_checksums/t/010_discard_caches.pl b/src/test/modules/test_checksums/t/010_discard_caches.pl new file mode 100644 index 00000000000..131fbc7ff7f --- /dev/null +++ b/src/test/modules/test_checksums/t/010_discard_caches.pl @@ -0,0 +1,49 @@ + +# Copyright (c) 2026, PostgreSQL Global Development Group + +# Enabling data checksums while the relcache is being invalidated. + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use DataChecksums::Utils; + +# debug_discard_caches only does anything where DISCARD_CACHES_ENABLED is +# defined, which follows USE_ASSERT_CHECKING; elsewhere the GUC rejects +# any value above zero and the server would refuse to start. +if (!check_pg_config('#define USE_ASSERT_CHECKING 1')) +{ + plan skip_all => 'this build does not have debug_discard_caches'; +} + +my $node = PostgreSQL::Test::Cluster->new('discard_caches'); +$node->init(no_data_checksums => 1); +$node->append_conf('postgresql.conf', 'debug_discard_caches = 1'); +$node->start; + +test_checksum_state($node, 'off'); + +# A little user data, so the worker has relations of its own to walk +# besides the catalogs. +$node->safe_psql('postgres', + 'CREATE TABLE t AS SELECT generate_series(1, 1000) AS a'); + +# The whole test: the worker must survive walking the cluster while every +# catalog access flushes the caches. +enable_data_checksums($node, wait => 'on'); + +test_checksum_state($node, 'on'); + +is($node->safe_psql('postgres', 'SELECT count(*) FROM t'), + '1000', 'the cluster survived enabling checksums'); + +$node->stop; + +done_testing(); -- 2.43.0