public inbox for [email protected]  
help / color / mirror / Atom feed
From: Daniel Gustafsson <[email protected]>
To: PostgreSQL Hackers <[email protected]>
Cc: Heikki Linnakangas <[email protected]>
Cc: Tomas Vondra <[email protected]>
Cc: Andres Freund <[email protected]>
Cc: Bernd Helmle <[email protected]>
Cc: Michael Paquier <[email protected]>
Cc: Michael Banck <[email protected]>
Cc: SATYANARAYANA NARLAPURAM <[email protected]>
Subject: Re: Changing the state of data checksums in a running cluster
Date: Thu, 23 Apr 2026 12:16:44 +0200
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
	<[email protected]>
	<5mv5vtbqj2xm2wmevsi22smyn32jtznva3ya3daih3ixh3onex@s5fyab63xt3c>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<CAHg+QDfRk4-S7DMmdbXJnQ-xF=sUpMAKuh8b83ObLqYVKx5QLA@mail.gmail.com>
	<[email protected]>

While performing extensive post-commit testing using the built-in TAP tests in
checksum_extended mode, Tomas observed a couple issues.  The testing was done
on multiple machines, ranging from a fast x86 machine to a much slower rpi4,
but they all performed a very large number of iterations.  The combination of
hardware and the large number of tests executed is likely why some of the
issues went unnoticed.

Below are the issues described in more detail:

1) race condition between checkpoint and checksum state changes

The main issue is a race condition causing invalid state transitions.
When analyzing the failure (which we couldn't synthetically reproduce, only
observe by running tests over and over for a long time) we realized that the
original coding was performing checksum state transitions while replaying
online checkpoints as well as redo checkpoints.

Updating checksum state while replaying online checkpoints is incorrect.  After
a crash we'll start with the REDO record, which initializes checksums to the
right state.  And then later the checksum state is updated by the regular
XLOG_CHECKSUMS entries.

Moreover, the checksum state in the XLOG_CHECKPOINT_ONLINE record can be stale,
because the value is determined at the checkpoint start, but the WAL entry
is added at the end.  If there is a concurrent checksum change, the value
written to the WAL record will be stale.  Replaying it will cause an "invalid
transition" error later, during the next checksum state update. The fix is to
remove the checksum update from the online checkpoint altogether.

In fact, there's no need for CreateCheckPoint to update checksum state
in the ControlFile. If the value is stale, it could make it permanent.
But it's unnecessary - the ControlFile is updated by the process
performing the checksum state change. So remove that.

This, along with an re-ordering updating the controlfile and procsignal barrier
enission fixes the issue.  The re-ordering makes sure that the controlfile is
always updated *before* a procsignalbarrier is emitted to avoid a race like the
one described below:

1. A barrier for off to inprogress-on is emitted
2. All active backends absorbs the barrier
  - All processes in the cluster are in state inprogress-on
3. A new backend b' forks, reads controlfile and sets state of off
4. The controlfile is updated
5. A new backend b'' forks, reads controlfile and sets state to inprogress-on

b' and b'' have different states, and b' has an incompatible state with the
rest of the cluster.  Re-ordering as done in the attached makes this go away.


2) race condition in launcher exit

Another timing related issue was that reverting to the "off" state then
launcher errors out had synchronization logic which was racy as it was relying
on the cached checksum state and not the value from XLogCtl.  The logic for
determining if a launcher/worker was already active was also fragile as it
started another launcher which would overwrite certain data in shared memory.
The attached patch inspects shared memory instead and use that to signal the
running launcher to either abort (disable), or change cost parameters on a
running enable process.  These fixes makes erroring out and going to off state
stable.


3) Concurrency issue with ProcSignalInit / InitLocalDataChecksumState

The checksum barriers must not be consumed before the initial value gets
properly set.  On very slow systems, there could potentially be multiple
checksum state transitions between a fork and InitLocalDataChecksumState.  In
such cases we might get failures due to incorrect transitions.

With the current code this is not a live issue, as there is no place checking
interrupts in between the two functions.  But it's fragile, as it's trivial to
break this by adding an elog() somewhere.  Which is what happened to us while
debugging the other issues.  So better to explicitly hold interrupts for a
brief moment.

To find the issues, and to validate their fix, Tomas developed a new testsuite
which is attached as a .txt.  This is not proposed for adding to v19, it is
included to showcase what was done, and what will be further hacked on for a
new suite during the v20 cycle.  It is gated behind PG_TEST_EXTRA and is
intended to be executed by select members of the buildfarm.

As part of this postcommit review we also found a few more cleanups and smaller
fixes which are included.  The patchset also includes the patch submitted
upthread by Satyanarayana Narlapuram.

More details can be found in the individual commit messages.

Unless there are objections I would like to go ahead with this fixup fairly soon.

--
Daniel Gustafsson


commit f502fa990e877c6e4d1d03518e524519d8e88806
Author: Tomas Vondra <[email protected]>
Date:   Wed Apr 15 12:50:06 2026 +0200

    POC: Test checksum state transitions using step through injection points

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index be92b6af20f..5b481784543 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4762,6 +4762,9 @@ SetDataChecksumsOnInProgress(void)
 	uint32	data_checksum_version;
 
 	elog(LOG, "SetDataChecksumsOnInProgress / start");
+
+	INJECTION_POINT("datachecksums-enable-inprogress-checksums-delay", NULL);
+
 	/*
 	 * The state transition is performed in a critical section with
 	 * checkpoints held off to provide crash safety.
@@ -4782,6 +4785,8 @@ SetDataChecksumsOnInProgress(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
+	INJECTION_POINT("datachecksums-enable-inprogress-checksums-after-xlogctl", NULL);
+
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 
 	elog(LOG, "SetDataChecksumsOnInProgress ControlFile->data_checksum_version  %u => %u",
@@ -4791,6 +4796,8 @@ SetDataChecksumsOnInProgress(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	INJECTION_POINT("datachecksums-enable-inprogress-checksums-after-controlfile", NULL);
+
 	elog(LOG, "SetDataChecksumsOnInProgress / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_ON)");
 	EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_ON);
 
@@ -4862,6 +4869,8 @@ SetDataChecksumsOn(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
+	INJECTION_POINT("datachecksums-enable-checksums-after-xlogctl", NULL);
+
 	/*
 	 * Update the controlfile before waiting since if we have an immediate
 	 * shutdown while waiting we want to come back up with checksums enabled.
@@ -4875,9 +4884,13 @@ SetDataChecksumsOn(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	INJECTION_POINT("datachecksums-enable-checksums-after-controlfile", NULL);
+
 	elog(LOG, "SetDataChecksumsOn / RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST)");
 	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 
+	INJECTION_POINT("datachecksums-enable-checksums-after-checkpoint", NULL);
+
 	elog(LOG, "SetDataChecksumsOn / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_VERSION)");
 	EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_VERSION);
 
@@ -4924,6 +4937,8 @@ SetDataChecksumsOff(void)
 	{
 		SpinLockRelease(&XLogCtl->info_lck);
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-delay", NULL);
+
 		START_CRIT_SECTION();
 		MyProc->delayChkptFlags |= DELAY_CHKPT_START;
 
@@ -4934,24 +4949,30 @@ SetDataChecksumsOff(void)
 		XLogCtl->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_OFF;
 		SpinLockRelease(&XLogCtl->info_lck);
 
-		elog(LOG, "SetDataChecksumsOff / XLogCtl->data_checksum_version %u = %u",
+		elog(LOG, "SetDataChecksumsOff / XLogCtl->data_checksum_version %u => %u",
 			 data_checksum_version, PG_DATA_CHECKSUM_INPROGRESS_OFF);
 
 		MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 		END_CRIT_SECTION();
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-after-xlogctl", NULL);
+
 		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 
-		elog(LOG, "SetDataChecksumsOff / ControlFile->data_checksum_version %u = %u",
+		elog(LOG, "SetDataChecksumsOff / ControlFile->data_checksum_version %u => %u",
 			 ControlFile->data_checksum_version, PG_DATA_CHECKSUM_INPROGRESS_OFF);
 
 		ControlFile->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_OFF;
 		UpdateControlFile();
 		LWLockRelease(ControlFileLock);
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-after-controlfile", NULL);
+
 		elog(LOG, "SetDataChecksumsOff / RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST)");
 		RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-after-checkpoint", NULL);
+
 		elog(LOG, "SetDataChecksumsOff / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_OFF)");
 		EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_OFF);
 
@@ -4971,6 +4992,8 @@ SetDataChecksumsOff(void)
 		SpinLockRelease(&XLogCtl->info_lck);
 	}
 
+	INJECTION_POINT("datachecksums-disable-checksums-delay", NULL);
+
 	START_CRIT_SECTION();
 	/* Ensure that we don't incur a checkpoint during disabling checksums */
 	MyProc->delayChkptFlags |= DELAY_CHKPT_START;
@@ -4988,6 +5011,8 @@ SetDataChecksumsOff(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
+	INJECTION_POINT("datachecksums-disable-checksums-after-xlogctl", NULL);
+
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 
 	elog(LOG, "SetDataChecksumsOff / ControlFile->data_checksum_version %u => %u",
@@ -4997,9 +5022,13 @@ SetDataChecksumsOff(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	INJECTION_POINT("datachecksums-disable-checksums-after-controlfile", NULL);
+
 	elog(LOG, "SetDataChecksumsOff / RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST)");
 	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 
+	INJECTION_POINT("datachecksums-disable-checksums-after-checkpoint", NULL);
+
 	elog(LOG, "SetDataChecksumsOff / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_OFF)");
 	EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_OFF);
 
diff --git a/src/test/modules/test_checksums/t/010_injection_2.pl b/src/test/modules/test_checksums/t/010_injection_2.pl
new file mode 100644
index 00000000000..a8ae9ffd151
--- /dev/null
+++ b/src/test/modules/test_checksums/t/010_injection_2.pl
@@ -0,0 +1,209 @@
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test suite for testing enabling data checksums in an online cluster with
+# injection point tests injecting failures into the processing
+
+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;
+
+# This test suite is expensive, or very expensive, to execute.  There are two
+# PG_TEST_EXTRA options for running it, "checksum" for a pared-down test suite
+# an "checksum_extended" for the full suite.  The full suite can run for hours
+# on slow or constrained systems.
+my $extended = undef;
+if ($ENV{PG_TEST_EXTRA})
+{
+	$extended = 1 if ($ENV{PG_TEST_EXTRA} =~ /\bchecksum_extended\b/);
+	plan skip_all => 'Expensive data checksums test disabled'
+	  unless ($ENV{PG_TEST_EXTRA} =~ /\bchecksum(_extended)?\b/);
+}
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# ---------------------------------------------------------------------------
+# Test cluster setup
+#
+
+# Initiate testcluster
+my $node = PostgreSQL::Test::Cluster->new('injection_node');
+$node->init(no_data_checksums => 1);
+$node->start;
+
+# Set up test environment
+$node->safe_psql('postgres', 'CREATE EXTENSION test_checksums;');
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+my $pgbench = undef;
+my $scalefactor = ($extended ? 10 : 1);
+my $node_loglocation = 0;
+
+$node->command_ok(
+	[
+		'pgbench', '-p', $node->port, '-i',
+		'-s', $scalefactor, '-q', 'postgres'
+	]);
+
+# Start a pgbench run in the background against the server specified via the
+# port passed as parameter.
+sub background_rw_pgbench
+{
+	my $port = shift;
+
+	# If a previous pgbench is still running, start by shutting it down.
+	$pgbench->finish if $pgbench;
+
+	my $clients = 1;
+	my $runtime = 2;
+
+	if ($extended)
+	{
+		# Randomize the number of pgbench clients a bit (range 1-16)
+		$clients = 1 + int(rand(15));
+		$runtime = 600;
+	}
+	my @cmd = ('pgbench', '-p', $port, '-T', $runtime, '-c', $clients);
+
+	# Randomize whether we spawn connections or not
+	push(@cmd, '-C') if ($extended && cointoss);
+	# Finally add the database name to use
+	push(@cmd, 'postgres');
+
+	$pgbench = IPC::Run::start(
+		\@cmd,
+		'<' => '/dev/null',
+		'>' => '/dev/null',
+		'2>' => '/dev/null',
+		IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default));
+}
+
+# Test checksum transition. The function has these arguments:
+#
+# - start checksum state (enabled/disabled)
+# - first - first checksum change
+# - second - second checksum change
+# - point - injection point the first change should wait on
+# - final - expected checksum state at the end
+#
+# The test puts the instance into the initial checksum state, triggers two
+# checksum changes, and verifies the final state is as expected. The first
+# state change is paused on a selected injection point, and unpaused after
+# the second change gets initiated.
+#
+# The injection point is triggered only by the datachecksum launcher, and
+# there can be only one such process. So there's no risk of hitting the
+# injection point by both changes.
+sub test_checksum_transition
+{
+	my ($start, $first, $second, $point, $final) = @_;
+
+	$node->safe_psql('postgres',
+		"SELECT '========== " . $start . " / " . $first . " / " . $second . " / " . $point . " / " . $final . " =========='");
+
+	note($start . " / " . $first . " / " . $second . " / " . $point . " / " . $final);
+
+	note('changing checksums into initial state: ' . $start);
+
+	enable_data_checksums($node, wait => 'on') if ($start eq 'enabled');
+	disable_data_checksums($node, wait => 'off') if ($start eq 'disabled');
+
+	note('attaching injection point: ' . $point);
+	$node->safe_psql('postgres',
+		"SELECT injection_points_attach('" . $point . "','wait');"
+	);
+
+	note("triggering first checksum change: " . $first);
+
+	enable_data_checksums($node) if ($first eq 'enable');
+	disable_data_checksums($node) if ($first eq 'disable');
+
+	note("waiting for the injection point to be hit");
+	$node->poll_query_until(
+		'postgres',
+		"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point . "'",
+		'1');
+
+	note("triggering second checksum change: " . $second);
+
+	enable_data_checksums($node) if ($second eq 'enable');
+	disable_data_checksums($node) if ($second eq 'disable');
+
+	note("waking and detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_wakeup('" . $point . "');");
+
+	note("detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_detach('" . $point . "');");
+
+	note('wait for the checksum launcher to exit');
+	$node->poll_query_until('postgres',
+			"SELECT count(*) = 0 "
+		  . "FROM pg_catalog.pg_stat_activity "
+		  . "WHERE backend_type = 'datachecksum launcher';");
+
+	test_checksum_state($node, $final);
+
+	# Since the log isn't being written to now, parse the log and check
+	# for instances of checksum verification failures.
+	my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile,
+		$node_loglocation);
+	unlike(
+		$log,
+		qr/page verification failed,.+\d$/,
+		"no checksum validation errors in primary log (during WAL recovery)"
+	);
+	$node_loglocation = -s $node->logfile;
+}
+
+# Start the test suite with pgbench running.
+background_rw_pgbench($node->port);
+
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-inprogress-checksums-delay', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-delay', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-after-xlogctl', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-after-controlfile', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-after-checkpoint', 'off');
+
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-inprogress-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-after-checkpoint', 'on');
+
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-after-checkpoint', 'off');
+
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-delay', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-delay', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-after-xlogctl', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-after-controlfile', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-after-checkpoint', 'on');
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_checksums/t/011_injection_checkpoint.pl b/src/test/modules/test_checksums/t/011_injection_checkpoint.pl
new file mode 100644
index 00000000000..ce1553e8ecd
--- /dev/null
+++ b/src/test/modules/test_checksums/t/011_injection_checkpoint.pl
@@ -0,0 +1,196 @@
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test suite for testing enabling data checksums in an online cluster with
+# injection point tests injecting failures into the processing
+
+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;
+
+# This test suite is expensive, or very expensive, to execute.  There are two
+# PG_TEST_EXTRA options for running it, "checksum" for a pared-down test suite
+# an "checksum_extended" for the full suite.  The full suite can run for hours
+# on slow or constrained systems.
+my $extended = undef;
+if ($ENV{PG_TEST_EXTRA})
+{
+	$extended = 1 if ($ENV{PG_TEST_EXTRA} =~ /\bchecksum_extended\b/);
+	plan skip_all => 'Expensive data checksums test disabled'
+	  unless ($ENV{PG_TEST_EXTRA} =~ /\bchecksum(_extended)?\b/);
+}
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# ---------------------------------------------------------------------------
+# Test cluster setup
+#
+
+# Initiate testcluster
+my $node = PostgreSQL::Test::Cluster->new('injection_node');
+$node->init(no_data_checksums => 1);
+$node->start;
+
+# Set up test environment
+$node->safe_psql('postgres', 'CREATE EXTENSION test_checksums;');
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+my $pgbench = undef;
+my $scalefactor = ($extended ? 10 : 1);
+my $node_loglocation = 0;
+
+$node->command_ok(
+	[
+		'pgbench', '-p', $node->port, '-i',
+		'-s', $scalefactor, '-q', 'postgres'
+	]);
+
+# Start a pgbench run in the background against the server specified via the
+# port passed as parameter.
+sub background_rw_pgbench
+{
+	my $port = shift;
+
+	# If a previous pgbench is still running, start by shutting it down.
+	$pgbench->finish if $pgbench;
+
+	my $clients = 1;
+	my $runtime = 2;
+
+	if ($extended)
+	{
+		# Randomize the number of pgbench clients a bit (range 1-16)
+		$clients = 1 + int(rand(15));
+		$runtime = 600;
+	}
+	my @cmd = ('pgbench', '-p', $port, '-T', $runtime, '-c', $clients);
+
+	# Randomize whether we spawn connections or not
+	push(@cmd, '-C') if ($extended && cointoss);
+	# Finally add the database name to use
+	push(@cmd, 'postgres');
+
+	$pgbench = IPC::Run::start(
+		\@cmd,
+		'<' => '/dev/null',
+		'>' => '/dev/null',
+		'2>' => '/dev/null',
+		IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default));
+}
+
+# Test checksum transition concurrent with a checkpoint.
+#
+# The function has these arguments:
+#
+# - start checksum state (enabled/disabled)
+# - change - checksum change to initiate
+# - point - injection point the first change should wait on
+# - final - expected checksum state at the end
+#
+# The test puts the instance into the initial checksum state, triggers a
+# checksum change concurrent with a checkpoint, and verifies the final state
+# is as expected. The state change is paused on a selected injection
+# point, and unpaused after performing a checkpoint.
+#
+# Finally, the instance is restarted (in either fast ot immediate mode),
+# the final checksum state is validated against the expected value, and
+# the server log is checked for checksum failures.
+sub test_checksum_transition
+{
+	my ($start, $change, $point, $final) = @_;
+
+	# Start the test suite with pgbench running.
+	background_rw_pgbench($node->port);
+
+	$node->safe_psql('postgres',
+		"SELECT '========== " . $start . " / " . $change . " / " . $point . " / " . $final . " =========='");
+
+	note($start . " / " . $change . " / " . $point . " / " . $final);
+
+	note('changing checksums into initial state: ' . $start);
+
+	enable_data_checksums($node, wait => 'on') if ($start eq 'enabled');
+	disable_data_checksums($node, wait => 'off') if ($start eq 'disabled');
+
+	note('attaching injection point: ' . $point);
+	$node->safe_psql('postgres',
+		"SELECT injection_points_attach('" . $point . "','wait');"
+	);
+
+	note("triggering checksum change: " . $change);
+
+	enable_data_checksums($node) if ($change eq 'enable');
+	disable_data_checksums($node) if ($change eq 'disable');
+
+	note("waiting for the injection point to be hit");
+	$node->poll_query_until(
+		'postgres',
+		"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point . "'",
+		'1');
+
+	note('checkpoint');
+	$node->safe_psql('postgres', "CHECKPOINT");
+
+	note("waking and detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_wakeup('" . $point . "');");
+
+	note("detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_detach('" . $point . "');");
+
+	note('wait for the checksum launcher to exit');
+	$node->poll_query_until('postgres',
+			"SELECT count(*) = 0 "
+		  . "FROM pg_catalog.pg_stat_activity "
+		  . "WHERE backend_type = 'datachecksum launcher';");
+
+	test_checksum_state($node, $final);
+
+	$node->stop(stopmode());
+	$node->start;
+
+	test_checksum_state($node, $final);
+
+	# Since the log isn't being written to now, parse the log and check
+	# for instances of checksum verification failures.
+	my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile,
+		$node_loglocation);
+	unlike(
+		$log,
+		qr/page verification failed,.+\d$/,
+		"no checksum validation errors in primary log (during WAL recovery)"
+	);
+	$node_loglocation = -s $node->logfile;
+}
+
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-checkpoint', 'on');
+
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-checkpoint', 'off');
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_checksums/t/012_injection_checkpoint_crash.pl b/src/test/modules/test_checksums/t/012_injection_checkpoint_crash.pl
new file mode 100644
index 00000000000..98e9c805c41
--- /dev/null
+++ b/src/test/modules/test_checksums/t/012_injection_checkpoint_crash.pl
@@ -0,0 +1,214 @@
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test suite for testing enabling data checksums in an online cluster with
+# injection point tests injecting failures into the processing
+
+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;
+
+# This test suite is expensive, or very expensive, to execute.  There are two
+# PG_TEST_EXTRA options for running it, "checksum" for a pared-down test suite
+# an "checksum_extended" for the full suite.  The full suite can run for hours
+# on slow or constrained systems.
+my $extended = undef;
+if ($ENV{PG_TEST_EXTRA})
+{
+	$extended = 1 if ($ENV{PG_TEST_EXTRA} =~ /\bchecksum_extended\b/);
+	plan skip_all => 'Expensive data checksums test disabled'
+	  unless ($ENV{PG_TEST_EXTRA} =~ /\bchecksum(_extended)?\b/);
+}
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# ---------------------------------------------------------------------------
+# Test cluster setup
+#
+
+# Initiate testcluster
+my $node = PostgreSQL::Test::Cluster->new('injection_node');
+$node->init(no_data_checksums => 1);
+$node->start;
+
+# Set up test environment
+$node->safe_psql('postgres', 'CREATE EXTENSION test_checksums;');
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+my $pgbench = undef;
+my $scalefactor = ($extended ? 10 : 1);
+my $node_loglocation = 0;
+
+$node->command_ok(
+	[
+		'pgbench', '-p', $node->port, '-i',
+		'-s', $scalefactor, '-q', 'postgres'
+	]);
+
+# Start a pgbench run in the background against the server specified via the
+# port passed as parameter.
+sub background_rw_pgbench
+{
+	my $port = shift;
+
+	# If a previous pgbench is still running, start by shutting it down.
+	$pgbench->finish if $pgbench;
+
+	my $clients = 1;
+	my $runtime = 2;
+
+	if ($extended)
+	{
+		# Randomize the number of pgbench clients a bit (range 1-16)
+		$clients = 1 + int(rand(15));
+		$runtime = 600;
+	}
+	my @cmd = ('pgbench', '-p', $port, '-T', $runtime, '-c', $clients);
+
+	# Randomize whether we spawn connections or not
+	push(@cmd, '-C') if ($extended && cointoss);
+	# Finally add the database name to use
+	push(@cmd, 'postgres');
+
+	$pgbench = IPC::Run::start(
+		\@cmd,
+		'<' => '/dev/null',
+		'>' => '/dev/null',
+		'2>' => '/dev/null',
+		IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default));
+}
+
+# Test checksum transition concurrent with a checkpoint.
+#
+# The function has these arguments:
+#
+# - start checksum state (enabled/disabled)
+# - change - checksum change to initiate
+# - point1 - injection point before checkpoint
+# - point2 - injection point after checkpoint
+# - final - expected checksum state at the end
+#
+# The test puts the instance into the initial checksum state, triggers a
+# checksum change that pauses on a selected injection point. Then performs
+# a checkpoint, unpauses the change so that it proceeds to a second
+# injection point.
+#
+# Then the instance is restarted in immediate mode to simulate failure,
+# and the final checksum state is validated against the expected value.
+# The server log is checked for checksum failures.
+sub test_checksum_transition
+{
+	my ($start, $change, $point1, $point2, $final) = @_;
+
+	# Start the test suite with pgbench running.
+	background_rw_pgbench($node->port);
+
+	$node->safe_psql('postgres',
+		"SELECT '========== " . $start . " / " . $change . " / " . $point1 . " / " . $final . " =========='");
+
+	note($start . " / " . $change . " / " . $point1 . " / " . $final);
+
+	note('changing checksums into initial state: ' . $start);
+
+	enable_data_checksums($node, wait => 'on') if ($start eq 'enabled');
+	disable_data_checksums($node, wait => 'off') if ($start eq 'disabled');
+
+	note('attaching injection point: ' . $point1);
+	$node->safe_psql('postgres',
+		"SELECT injection_points_attach('" . $point1 . "','wait');"
+	);
+
+	if (defined($point2))
+	{
+		note('attaching injection point: ' . $point2);
+		$node->safe_psql('postgres',
+			"SELECT injection_points_attach('" . $point2 . "','wait');"
+		);
+	}
+
+	note("triggering checksum change: " . $change);
+
+	enable_data_checksums($node) if ($change eq 'enable');
+	disable_data_checksums($node) if ($change eq 'disable');
+
+	note("waiting for the injection point to be hit");
+	$node->poll_query_until(
+		'postgres',
+		"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point1 . "'",
+		'1');
+
+	note('checkpoint');
+	$node->safe_psql('postgres', "CHECKPOINT");
+
+	note("waking and detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_wakeup('" . $point1 . "');");
+
+	note("detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_detach('" . $point1 . "');");
+
+	if (defined($point2))
+	{
+		note("waiting for the injection point to be hit");
+		$node->poll_query_until(
+			'postgres',
+			"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point2 . "'",
+			'1');
+	}
+	else
+	{
+		note('wait for the checksum launcher to exit');
+		$node->poll_query_until('postgres',
+				"SELECT count(*) = 0 "
+			  . "FROM pg_catalog.pg_stat_activity "
+			  . "WHERE backend_type = 'datachecksum launcher';");
+	}
+
+	$node->stop('immediate');
+	$node->start;
+
+	test_checksum_state($node, $final);
+
+	# Since the log isn't being written to now, parse the log and check
+	# for instances of checksum verification failures.
+	my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile,
+		$node_loglocation);
+	unlike(
+		$log,
+		qr/page verification failed,.+\d$/,
+		"no checksum validation errors in primary log (during WAL recovery)"
+	);
+	$node_loglocation = -s $node->logfile;
+}
+
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-delay', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'datachecksums-enable-checksums-delay', 'off');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-delay', 'datachecksums-enable-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-xlogctl', 'datachecksums-enable-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-controlfile', 'datachecksums-enable-checksums-after-checkpoint', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-checkpoint', undef, 'on');
+
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-delay', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'datachecksums-disable-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-delay', 'datachecksums-disable-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-xlogctl', 'datachecksums-disable-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-controlfile', 'datachecksums-disable-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-checkpoint', undef, 'off');
+
+$node->stop;
+done_testing();


Attachments:

  [application/octet-stream] 0008-Fix-data_checksum-GUC-show_hook.patch (971B, ../[email protected]/2-0008-Fix-data_checksum-GUC-show_hook.patch)
  download | inline diff:
From 0a2b961f90427937796b5bca549b04e20958cd0f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:44:47 +0200
Subject: [PATCH 8/8] Fix data_checksum GUC show_hook

Commit f19c0eccae erroneously omitted the show_hook for the
data_checksum GUC.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/xxx
---
 src/backend/utils/misc/guc_parameters.dat | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 83af594d4af..afaa058b046 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -585,6 +585,7 @@
   variable => 'data_checksums',
   boot_val => 'PG_DATA_CHECKSUM_OFF',
   options => 'data_checksums_options',
+  show_hook => 'show_data_checksums',
 },
 
 # Can't be set by ALTER SYSTEM as it can lead to recursive definition
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] 0007-Improve-database-detection-logic-in-datachecksumswor.patch (2.3K, ../[email protected]/3-0007-Improve-database-detection-logic-in-datachecksumswor.patch)
  download | inline diff:
From 3e0fbc07bade81a423b668d975111bb02067442f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:44:40 +0200
Subject: [PATCH 7/8] Improve database detection logic in datachecksumsworker

The worker need to know whether a database which failed checksum
processing still exists, or has been dropped.  This improves the
detection logic by checking for being partially dropped.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/xxx
---
 src/backend/postmaster/datachecksum_state.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index c382b954d91..53b66cbbcf6 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -843,8 +843,7 @@ ProcessDatabase(DataChecksumsWorkerDatabase *db)
 
 		/*
 		 * Heuristic to see if the database was dropped, and if it was we can
-		 * treat it as not an error, else treat as fatal and error out. TODO:
-		 * this could probably be improved with a tighter check.
+		 * treat it as not an error, else treat as fatal and error out.
 		 */
 		if (DatabaseExists(db->dboid))
 			return DATACHECKSUMSWORKER_FAILED;
@@ -1305,7 +1304,9 @@ DataChecksumsShmemRequest(void *arg)
  * DatabaseExists
  *
  * Scans the system catalog to check if a database with the given Oid exists
- * and returns true if it is found, else false.
+ * and returns true if it is found and valid, else false. Note, we cannot use
+ * database_is_invalid_oid here as it will ERROR out, and we want to gracefully
+ * handle errors.
  */
 static bool
 DatabaseExists(Oid dboid)
@@ -1315,6 +1316,7 @@ DatabaseExists(Oid dboid)
 	SysScanDesc scan;
 	bool		found;
 	HeapTuple	tuple;
+	Form_pg_database pg_database_tuple;
 
 	StartTransactionCommand();
 
@@ -1328,6 +1330,14 @@ DatabaseExists(Oid dboid)
 	tuple = systable_getnext(scan);
 	found = HeapTupleIsValid(tuple);
 
+	/* If the Oid exists, ensure that it's not partially dropped */
+	if (found)
+	{
+		pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple);
+		if (database_is_invalid_form(pg_database_tuple))
+			found = false;
+	}
+
 	systable_endscan(scan);
 	table_close(rel, AccessShareLock);
 
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] 0006-Improve-handling-of-concurrent-checksum-requests.patch (9.8K, ../[email protected]/4-0006-Improve-handling-of-concurrent-checksum-requests.patch)
  download | inline diff:
From 7517ce4a6c33be0f75780ade699afc1e807cf997 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:44:32 +0200
Subject: [PATCH 6/8] Improve handling of concurrent checksum requests

When pg_{enable|disable}_data_checksums is called while checksums are
being enabled of disabled, the already running launcher is detected
and the new desired state is recorded.  Processing will then pick up
the new state and change it's operation to fulfill the new request.

If the same state is requested but updated cost values, the new cost
values will take effect on the next relation processed.  The previous
coding had a complex logic of starting a new launcher for this, which
ss now avoided with the shared mem structure instead used to signal
current processing.

This makes the logic more robust, and fixes a bug where the launcher
would erroneously revert back to the "off" state.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/xxx
---
 src/backend/access/transam/xlog.c           | 33 +++++++-
 src/backend/postmaster/datachecksum_state.c | 85 +++++++++++++++------
 src/include/access/xlog.h                   |  2 +
 3 files changed, 96 insertions(+), 24 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 9b1a10dbd1c..49772510855 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4682,10 +4682,41 @@ DataChecksumsNeedWrite(void)
 			LocalDataChecksumState == PG_DATA_CHECKSUM_INPROGRESS_OFF);
 }
 
+
+bool
+DataChecksumsOff(void)
+{
+	bool		ret;
+
+	SpinLockAcquire(&XLogCtl->info_lck);
+	ret = (XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_OFF);
+	SpinLockRelease(&XLogCtl->info_lck);
+
+	return ret;
+}
+
+bool
+DataChecksumsOn(void)
+{
+	bool		ret;
+
+	SpinLockAcquire(&XLogCtl->info_lck);
+	ret = (XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_VERSION);
+	SpinLockRelease(&XLogCtl->info_lck);
+
+	return ret;
+}
+
 bool
 DataChecksumsInProgressOn(void)
 {
-	return LocalDataChecksumState == PG_DATA_CHECKSUM_INPROGRESS_ON;
+	bool		ret;
+
+	SpinLockAcquire(&XLogCtl->info_lck);
+	ret = (XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_ON);
+	SpinLockRelease(&XLogCtl->info_lck);
+
+	return ret;
 }
 
 /*
diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 5e14803b0b0..c382b954d91 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -235,7 +235,7 @@ typedef struct ChecksumBarrierCondition
 	int			to;
 } ChecksumBarrierCondition;
 
-static const ChecksumBarrierCondition checksum_barriers[7] =
+static const ChecksumBarrierCondition checksum_barriers[9] =
 {
 	/*
 	 * Disabling checksums: If checksums are currently enabled, disabling must
@@ -267,6 +267,13 @@ static const ChecksumBarrierCondition checksum_barriers[7] =
 	 * set to off since we cannot reach on at that point.
 	 */
 	{PG_DATA_CHECKSUM_INPROGRESS_ON, PG_DATA_CHECKSUM_INPROGRESS_OFF},
+
+	/*
+	 * Transitions that can happen when a new request is made while another is
+	 * currently being processed.
+	 */
+	{PG_DATA_CHECKSUM_INPROGRESS_OFF, PG_DATA_CHECKSUM_INPROGRESS_ON},
+	{PG_DATA_CHECKSUM_OFF, PG_DATA_CHECKSUM_INPROGRESS_OFF},
 };
 
 /*
@@ -368,6 +375,15 @@ const ShmemCallbacks DataChecksumsShmemCallbacks = {
 	.request_fn = DataChecksumsShmemRequest,
 };
 
+#define CHECK_FOR_ABORT_REQUEST() \
+	do {															\
+		LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);			\
+		if (DataChecksumState->launch_operation != operation)		\
+			abort_requested = true;									\
+		LWLockRelease(DataChecksumsWorkerLock);						\
+	} while (0)
+
+
 /*****************************************************************************
  * Functionality for manipulating the data checksum state in the cluster
  */
@@ -557,7 +573,6 @@ StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 	BackgroundWorker bgw;
 	BackgroundWorkerHandle *bgw_handle;
 	bool		running;
-	DataChecksumsWorkerOperation launcher_running_op;
 
 #ifdef USE_ASSERT_CHECKING
 	/* The cost delay settings have no effect when disabling */
@@ -576,8 +591,6 @@ StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 
 	/* Is the launcher already running? If so, what is it doing? */
 	running = DataChecksumState->launcher_running;
-	if (running)
-		launcher_running_op = DataChecksumState->operation;
 
 	LWLockRelease(DataChecksumsWorkerLock);
 
@@ -594,13 +607,17 @@ StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 	 * the launcher has had a chance to start up, we still end up launching it
 	 * twice.  That's OK, the second invocation will see that a launcher is
 	 * already running and exit quickly.
-	 *
-	 * TODO: We could optimize here and skip launching the launcher, if we are
-	 * already in the desired state, i.e. if the checksums are already enabled
-	 * and you call pg_enable_data_checksums().
 	 */
 	if (!running)
 	{
+		if ((op == ENABLE_DATACHECKSUMS && DataChecksumsOn()) ||
+			(op == DISABLE_DATACHECKSUMS && DataChecksumsOff()))
+		{
+			ereport(LOG,
+					errmsg("data checksums already in desired state, exiting"));
+			return;
+		}
+
 		/*
 		 * Prepare the BackgroundWorker and launch it.
 		 */
@@ -622,9 +639,8 @@ StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 	}
 	else
 	{
-		if (launcher_running_op == op)
-			ereport(ERROR,
-					errmsg("data checksum processing already running"));
+		ereport(LOG,
+				errmsg("data checksum processing already running"));
 	}
 }
 
@@ -998,11 +1014,8 @@ WaitForAllTransactionsToFinish(void)
 					errhint("Data checksums processing must be restarted manually after cluster restart."));
 
 		CHECK_FOR_INTERRUPTS();
+		CHECK_FOR_ABORT_REQUEST();
 
-		LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
-		if (DataChecksumState->launch_operation != operation)
-			abort_requested = true;
-		LWLockRelease(DataChecksumsWorkerLock);
 		if (abort_requested)
 			break;
 	}
@@ -1185,7 +1198,9 @@ ProcessAllDatabases(void)
 	int			cumulative_total = 0;
 
 	/* Set up so first run processes shared catalogs, not once in every db */
+	LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 	DataChecksumState->process_shared_catalogs = true;
+	LWLockRelease(DataChecksumsWorkerLock);
 
 	/* Get a list of all databases to process */
 	WaitForAllTransactionsToFinish();
@@ -1261,7 +1276,9 @@ ProcessAllDatabases(void)
 		 * When one database has completed, it will have done shared catalogs
 		 * so we don't have to process them again.
 		 */
+		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 		DataChecksumState->process_shared_catalogs = false;
+		LWLockRelease(DataChecksumsWorkerLock);
 	}
 
 	FreeDatabaseList(DatabaseList);
@@ -1503,7 +1520,6 @@ DataChecksumsWorkerMain(Datum arg)
 	 * implementation detail and care should be taken to avoid it bleeding
 	 * through to the user to avoid confusion.
 	 */
-	Assert(DataChecksumState->operation == ENABLE_DATACHECKSUMS);
 	VacuumCostDelay = DataChecksumState->cost_delay;
 	VacuumCostLimit = DataChecksumState->cost_limit;
 	VacuumCostActive = (VacuumCostDelay > 0);
@@ -1539,8 +1555,6 @@ DataChecksumsWorkerMain(Datum arg)
 	rels_done = 0;
 	foreach_oid(reloid, RelationList)
 	{
-		CHECK_FOR_INTERRUPTS();
-
 		if (!ProcessSingleRelationByOid(reloid, strategy))
 		{
 			aborted = true;
@@ -1549,12 +1563,38 @@ DataChecksumsWorkerMain(Datum arg)
 
 		pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_RELS_DONE,
 									 ++rels_done);
+		CHECK_FOR_INTERRUPTS();
+		CHECK_FOR_ABORT_REQUEST();
+
+		if (abort_requested)
+			break;
+
+		/* Check if the cost settings changed during runtime */
+		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
+		if ((DataChecksumState->launch_cost_delay != DataChecksumState->cost_delay)
+			|| (DataChecksumState->launch_cost_limit != DataChecksumState->cost_limit))
+		{
+			VacuumCostDelay = DataChecksumState->launch_cost_delay;
+			VacuumCostLimit = DataChecksumState->launch_cost_limit;
+			VacuumCostActive = (VacuumCostDelay > 0);
+
+			FreeAccessStrategy(strategy);
+			strategy = GetAccessStrategy(BAS_VACUUM);
+			DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
+			DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
+		}
+		LWLockRelease(DataChecksumsWorkerLock);
+
 	}
+
 	list_free(RelationList);
+	FreeAccessStrategy(strategy);
 
-	if (aborted)
+	if (aborted || abort_requested)
 	{
+		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 		DataChecksumState->success = DATACHECKSUMSWORKER_ABORTED;
+		LWLockRelease(DataChecksumsWorkerLock);
 		ereport(DEBUG1,
 				errmsg("data checksum processing aborted in database OID %u",
 					   dboid));
@@ -1619,15 +1659,14 @@ DataChecksumsWorkerMain(Datum arg)
 						 3000,
 						 WAIT_EVENT_CHECKSUM_ENABLE_TEMPTABLE_WAIT);
 
-		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
-		aborted = DataChecksumState->launch_operation != operation;
-		LWLockRelease(DataChecksumsWorkerLock);
-
 		CHECK_FOR_INTERRUPTS();
+		CHECK_FOR_ABORT_REQUEST();
 
 		if (aborted || abort_requested)
 		{
+			LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 			DataChecksumState->success = DATACHECKSUMSWORKER_ABORTED;
+			LWLockRelease(DataChecksumsWorkerLock);
 			ereport(LOG,
 					errmsg("data checksum processing aborted in database OID %u",
 						   dboid));
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 437b4f32349..4dd98624204 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -249,6 +249,8 @@ extern uint64 GetSystemIdentifier(void);
 extern char *GetMockAuthenticationNonce(void);
 extern bool DataChecksumsNeedWrite(void);
 extern bool DataChecksumsNeedVerify(void);
+extern bool DataChecksumsOn(void);
+extern bool DataChecksumsOff(void);
 extern bool DataChecksumsInProgressOn(void);
 extern void SetDataChecksumsOnInProgress(void);
 extern void SetDataChecksumsOn(void);
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] 0005-Typo-and-spelling-fixups-for-online-checksums.patch (2.6K, ../[email protected]/5-0005-Typo-and-spelling-fixups-for-online-checksums.patch)
  download | inline diff:
From ae0f05e8134cde61895b37918f0e968ae0ef1e32 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:44:28 +0200
Subject: [PATCH 5/8] Typo and spelling fixups for online checksums

A collection of spelling, wording and punctuation fixups for the code
documentation from postcommit review.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/xxx
---
 src/backend/postmaster/datachecksum_state.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 6d66322c625..5e14803b0b0 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -86,7 +86,7 @@
  * failing to validate the data checksum on the page when reading it.
  *
  * When processing starts all backends belong to one of the below sets, with
- * one if Bd and Bi being empty:
+ * one of Bd and Bi being empty:
  *
  * Bg: Backend updating the global state and emitting the procsignalbarrier
  * Bd: Backends in "off" state
@@ -286,7 +286,7 @@ typedef struct DataChecksumsStateStruct
 	int			launch_cost_limit;
 
 	/*
-	 * Is a launcher process is currently running?  This is set by the main
+	 * Is a launcher process currently running?  This is set by the main
 	 * launcher process, after it has read the above launch_* parameters.
 	 */
 	bool		launcher_running;
@@ -809,7 +809,7 @@ ProcessDatabase(DataChecksumsWorkerDatabase *db)
 	{
 		/*
 		 * If the worker managed to start, and stop, before we got to waiting
-		 * for it we can se a STOPPED status here without it being a failure.
+		 * for it we can see a STOPPED status here without it being a failure.
 		 */
 		if (DataChecksumState->success == DATACHECKSUMSWORKER_SUCCESSFUL)
 		{
@@ -1288,7 +1288,7 @@ DataChecksumsShmemRequest(void *arg)
  * DatabaseExists
  *
  * Scans the system catalog to check if a database with the given Oid exists
- * and returns true if it is found  else false.
+ * and returns true if it is found, else false.
  */
 static bool
 DatabaseExists(Oid dboid)
@@ -1451,7 +1451,7 @@ BuildRelationList(bool temp_relations, bool include_shared)
 /*
  * DataChecksumsWorkerMain
  *
- * Main function for enabling checksums in a single database, This is the
+ * Main function for enabling checksums in a single database. This is the
  * function set as the bgw_function_name in the dynamic background worker
  * process initiated for each database by the worker launcher. After enabling
  * data checksums in each applicable relation in the database, it will wait for
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] 0004-Fix-invalid-checksum-state-transition-in-checkpoints.patch (13.1K, ../[email protected]/6-0004-Fix-invalid-checksum-state-transition-in-checkpoints.patch)
  download | inline diff:
From cf5de7abe443acc311f4880499be520f00a7bf5e Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:44:22 +0200
Subject: [PATCH 4/8] Fix invalid checksum state transition in checkpoints

Commit 78e950cb8 added checksum state handling to all XLOG_CHECKPOINT
records which caused unnecessary state transitions and emission of
procsignal barriers.  Remove as only the _REDO record need to handle
checksum state.  Barrier emission is also consistently made after
controlfile updates to avoid race conditions.

Additionelly, interrupts are held between calling ProcSignalInit and
InitLocalDataChecksumState to remove a window where otherwise invalid
state transitions can happen.

Also remove a pointless assertion on Controlfile which will never hit.

Author: Tomas Vondra <[email protected]>
Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/xxx
---
 src/backend/access/transam/xlog.c           | 106 ++++++--------------
 src/backend/postmaster/auxprocess.c         |  10 ++
 src/backend/postmaster/datachecksum_state.c |   8 +-
 src/backend/utils/init/postinit.c           |  10 ++
 4 files changed, 55 insertions(+), 79 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f74d7a2ab1a..9b1a10dbd1c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4723,8 +4723,6 @@ SetDataChecksumsOnInProgress(void)
 {
 	uint64		barrier;
 
-	Assert(ControlFile != NULL);
-
 	/*
 	 * The state transition is performed in a critical section with
 	 * checkpoints held off to provide crash safety.
@@ -4738,25 +4736,16 @@ SetDataChecksumsOnInProgress(void)
 	XLogCtl->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_ON;
 	SpinLockRelease(&XLogCtl->info_lck);
 
-	barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_ON);
-
-	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
-	END_CRIT_SECTION();
-
-	/*
-	 * Update the controlfile before waiting since if we have an immediate
-	 * shutdown while waiting we want to come back up with checksums enabled.
-	 */
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 	ControlFile->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_ON;
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
-	/*
-	 * Await state change in all backends to ensure that all backends are in
-	 * "inprogress-on". Once done we know that all backends are writing data
-	 * checksums.
-	 */
+	barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_ON);
+
+	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
+	END_CRIT_SECTION();
+
 	WaitForProcSignalBarrier(barrier);
 }
 
@@ -4787,8 +4776,6 @@ SetDataChecksumsOn(void)
 {
 	uint64		barrier;
 
-	Assert(ControlFile != NULL);
-
 	SpinLockAcquire(&XLogCtl->info_lck);
 
 	/*
@@ -4818,11 +4805,6 @@ SetDataChecksumsOn(void)
 	XLogCtl->data_checksum_version = PG_DATA_CHECKSUM_VERSION;
 	SpinLockRelease(&XLogCtl->info_lck);
 
-	barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_ON);
-
-	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
-	END_CRIT_SECTION();
-
 	/*
 	 * Update the controlfile before waiting since if we have an immediate
 	 * shutdown while waiting we want to come back up with checksums enabled.
@@ -4832,12 +4814,12 @@ SetDataChecksumsOn(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
-	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
+	barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_ON);
 
-	/*
-	 * Await state transition to "on" in all backends. When done we know that
-	 * data checksums are both written and verified in all backends.
-	 */
+	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
+	END_CRIT_SECTION();
+
+	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 	WaitForProcSignalBarrier(barrier);
 }
 
@@ -4859,8 +4841,6 @@ SetDataChecksumsOff(void)
 {
 	uint64		barrier;
 
-	Assert(ControlFile != NULL);
-
 	SpinLockAcquire(&XLogCtl->info_lck);
 
 	/* If data checksums are already disabled there is nothing to do */
@@ -4891,22 +4871,17 @@ SetDataChecksumsOff(void)
 		XLogCtl->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_OFF;
 		SpinLockRelease(&XLogCtl->info_lck);
 
+		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
+		ControlFile->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_OFF;
+		UpdateControlFile();
+		LWLockRelease(ControlFileLock);
+
 		barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_OFF);
 
 		MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 		END_CRIT_SECTION();
 
-		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
-		ControlFile->data_checksum_version = PG_DATA_CHECKSUM_OFF;
-		UpdateControlFile();
-		LWLockRelease(ControlFileLock);
-
 		RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
-
-		/*
-		 * Update local state in all backends to ensure that any backend in
-		 * "on" state is changed to "inprogress-off".
-		 */
 		WaitForProcSignalBarrier(barrier);
 
 		/*
@@ -4935,18 +4910,17 @@ SetDataChecksumsOff(void)
 	XLogCtl->data_checksum_version = PG_DATA_CHECKSUM_OFF;
 	SpinLockRelease(&XLogCtl->info_lck);
 
-	barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_OFF);
-
-	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
-	END_CRIT_SECTION();
-
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 	ControlFile->data_checksum_version = PG_DATA_CHECKSUM_OFF;
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
-	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
+	barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_OFF);
 
+	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
+	END_CRIT_SECTION();
+
+	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 	WaitForProcSignalBarrier(barrier);
 }
 
@@ -4961,6 +4935,7 @@ SetDataChecksumsOff(void)
 void
 InitLocalDataChecksumState(void)
 {
+	Assert(InterruptHoldoffCount > 0);
 	SpinLockAcquire(&XLogCtl->info_lck);
 	SetLocalDataChecksumState(XLogCtl->data_checksum_version);
 	SpinLockRelease(&XLogCtl->info_lck);
@@ -5427,7 +5402,6 @@ XLOGShmemInit(void *arg)
 
 	/* Use the checksum info from control file */
 	XLogCtl->data_checksum_version = ControlFile->data_checksum_version;
-
 	SetLocalDataChecksumState(XLogCtl->data_checksum_version);
 
 	SpinLockInit(&XLogCtl->Insert.insertpos_lck);
@@ -7518,7 +7492,9 @@ CreateCheckPoint(int flags)
 	 * Get the current data_checksum_version value from xlogctl, valid at the
 	 * time of the checkpoint.
 	 */
+	SpinLockAcquire(&XLogCtl->info_lck);
 	checkPoint.dataChecksumState = XLogCtl->data_checksum_version;
+	SpinLockRelease(&XLogCtl->info_lck);
 
 	if (shutdown)
 	{
@@ -7639,10 +7615,6 @@ CreateCheckPoint(int flags)
 		checkPoint.nextOid += TransamVariables->oidCount;
 	LWLockRelease(OidGenLock);
 
-	SpinLockAcquire(&XLogCtl->info_lck);
-	checkPoint.dataChecksumState = XLogCtl->data_checksum_version;
-	SpinLockRelease(&XLogCtl->info_lck);
-
 	checkPoint.logicalDecodingEnabled = IsLogicalDecodingEnabled();
 
 	MultiXactGetCheckptMulti(shutdown,
@@ -7792,9 +7764,6 @@ CreateCheckPoint(int flags)
 	ControlFile->minRecoveryPoint = InvalidXLogRecPtr;
 	ControlFile->minRecoveryPointTLI = 0;
 
-	/* make sure we start with the checksum version as of the checkpoint */
-	ControlFile->data_checksum_version = checkPoint.dataChecksumState;
-
 	/*
 	 * Persist unloggedLSN value. It's reset on crash recovery, so this goes
 	 * unused on non-shutdown checkpoints, but seems useful to store it always
@@ -8871,11 +8840,6 @@ xlog_redo(XLogReaderState *record)
 		MultiXactAdvanceOldest(checkPoint.oldestMulti,
 							   checkPoint.oldestMultiDB);
 
-		SpinLockAcquire(&XLogCtl->info_lck);
-		XLogCtl->data_checksum_version = checkPoint.dataChecksumState;
-		SetLocalDataChecksumState(checkPoint.dataChecksumState);
-		SpinLockRelease(&XLogCtl->info_lck);
-
 		/*
 		 * No need to set oldestClogXid here as well; it'll be set when we
 		 * redo an xl_clog_truncate if it changed since initialization.
@@ -8936,6 +8900,8 @@ xlog_redo(XLogReaderState *record)
 		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 		ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
 		ControlFile->data_checksum_version = checkPoint.dataChecksumState;
+
+		UpdateControlFile();
 		LWLockRelease(ControlFileLock);
 
 		/*
@@ -8962,8 +8928,6 @@ xlog_redo(XLogReaderState *record)
 	{
 		CheckPoint	checkPoint;
 		TimeLineID	replayTLI;
-		bool		new_state = false;
-		int			old_state;
 
 		memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint));
 		/* In an ONLINE checkpoint, treat the XID counter as a minimum */
@@ -9002,8 +8966,6 @@ xlog_redo(XLogReaderState *record)
 		/* ControlFile->checkPointCopy always tracks the latest ckpt XID */
 		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 		ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
-		old_state = ControlFile->data_checksum_version;
-		ControlFile->data_checksum_version = checkPoint.dataChecksumState;
 		LWLockRelease(ControlFileLock);
 
 		/* TLI should not change in an on-line checkpoint */
@@ -9015,18 +8977,6 @@ xlog_redo(XLogReaderState *record)
 
 		RecoveryRestartPoint(&checkPoint, record);
 
-		/*
-		 * If the data checksum state change we need to emit a barrier.
-		 */
-		SpinLockAcquire(&XLogCtl->info_lck);
-		XLogCtl->data_checksum_version = checkPoint.dataChecksumState;
-		if (checkPoint.dataChecksumState != old_state)
-			new_state = true;
-		SpinLockRelease(&XLogCtl->info_lck);
-
-		if (new_state)
-			EmitAndWaitDataChecksumsBarrier(checkPoint.dataChecksumState);
-
 		/*
 		 * After replaying a checkpoint record, free all smgr objects.
 		 * Otherwise we would never do so for dropped relations, as the
@@ -9195,6 +9145,7 @@ xlog_redo(XLogReaderState *record)
 
 		SpinLockAcquire(&XLogCtl->info_lck);
 		XLogCtl->data_checksum_version = redo_rec.data_checksum_version;
+		SetLocalDataChecksumState(redo_rec.data_checksum_version);
 		if (redo_rec.data_checksum_version != ControlFile->data_checksum_version)
 			new_state = true;
 		SpinLockRelease(&XLogCtl->info_lck);
@@ -9268,6 +9219,11 @@ xlog2_redo(XLogReaderState *record)
 		XLogCtl->data_checksum_version = state.new_checksum_state;
 		SpinLockRelease(&XLogCtl->info_lck);
 
+		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
+		ControlFile->data_checksum_version = state.new_checksum_state;
+		UpdateControlFile();
+		LWLockRelease(ControlFileLock);
+
 		/*
 		 * Block on a procsignalbarrier to await all processes having seen the
 		 * change to checksum status. Once the barrier has been passed we can
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index 8fdc518b3a1..421502fa1cb 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -68,6 +68,14 @@ AuxiliaryProcessMainCommon(void)
 
 	BaseInit();
 
+	/*
+	 * Prevent consuming interrups between setting ProcSignalInit and setting
+	 * the initial local data checksum value.  If a barrier is emitted, and
+	 * absorbed, before local cached state is initialized the state transition
+	 * can be invalid.
+	 */
+	HOLD_INTERRUPTS();
+
 	ProcSignalInit(NULL, 0);
 
 	/*
@@ -88,6 +96,8 @@ AuxiliaryProcessMainCommon(void)
 	 */
 	InitLocalDataChecksumState();
 
+	RESUME_INTERRUPTS();
+
 	/*
 	 * Auxiliary processes don't run transactions, but they may need a
 	 * resource owner anyway to manage buffer pins acquired outside
diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index e62a36b0af5..6d66322c625 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -263,8 +263,8 @@ static const ChecksumBarrierCondition checksum_barriers[7] =
 	{PG_DATA_CHECKSUM_INPROGRESS_OFF, PG_DATA_CHECKSUM_VERSION},
 
 	/*
-	 * If checksums are being enabled when launcher_exit is executed, state
-	 * is set to off since we cannot reach on at that point.
+	 * If checksums are being enabled when launcher_exit is executed, state is
+	 * set to off since we cannot reach on at that point.
 	 */
 	{PG_DATA_CHECKSUM_INPROGRESS_ON, PG_DATA_CHECKSUM_INPROGRESS_OFF},
 };
@@ -1287,8 +1287,8 @@ DataChecksumsShmemRequest(void *arg)
 /*
  * DatabaseExists
  *
- * Scans the system catalog to check if a database with the given Oid exist
- * and returns true if it is found, else false.
+ * Scans the system catalog to check if a database with the given Oid exists
+ * and returns true if it is found  else false.
  */
 static bool
 DatabaseExists(Oid dboid)
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6f074013aa9..b2c80b0bd1e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -756,6 +756,14 @@ InitPostgres(const char *in_dbname, Oid dboid,
 	 */
 	SharedInvalBackendInit(false);
 
+	/*
+	 * Prevent consuming interrups between setting ProcSignalInit and setting
+	 * the initial local data checksum value.  If a barrier is emitted, and
+	 * absorbed, before local cached state is initialized the state transition
+	 * can be invalid.
+	 */
+	HOLD_INTERRUPTS();
+
 	ProcSignalInit(MyCancelKey, MyCancelKeyLength);
 
 	/*
@@ -776,6 +784,8 @@ InitPostgres(const char *in_dbname, Oid dboid,
 	 */
 	InitLocalDataChecksumState();
 
+	RESUME_INTERRUPTS();
+
 	/*
 	 * Also set up timeout handlers needed for backend operation.  We need
 	 * these in every case except bootstrap.
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] 0003-Handle-data_checksum-state-changes-during-launcher_e.patch (3.2K, ../[email protected]/7-0003-Handle-data_checksum-state-changes-during-launcher_e.patch)
  download | inline diff:
From b24179efcd960c15a1fa2309bb5107c5625e33e8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:44:16 +0200
Subject: [PATCH 3/8] Handle data_checksum state changes during launcher_exit

When erroring out from the datachecksums launcher during data checksum
enabling, before state has transitioned to "on", we revert back to the
"off" state.  Since checksums weren't enabled, there is no use staying
in an inprogress state since the checksum launcher currently doesn't
support restarting from where it left off.  Should restartability get
added in the future, this would need to be revisited.  This state
transition was however missing from the allowed transitions in the
statemachine causing an error.

Author: Daniel Gustafsson <[email protected]>
Discussion: https://postgr.es/m/xxx
---
 src/backend/access/transam/xlog.c           | 15 ++++++++-------
 src/backend/postmaster/datachecksum_state.c |  8 +++++++-
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e39af79c03b..f74d7a2ab1a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4871,13 +4871,14 @@ SetDataChecksumsOff(void)
 	}
 
 	/*
-	 * If data checksums are currently enabled we first transition to the
-	 * "inprogress-off" state during which backends continue to write
-	 * checksums without verifying them. When all backends are in
-	 * "inprogress-off" the next transition to "off" can be performed, after
-	 * which all data checksum processing is disabled.
-	 */
-	if (XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_VERSION)
+	 * If data checksums are currently enabled, or in the process of being
+	 * enabled, we first transition to the "inprogress-off" state during which
+	 * backends continue to write checksums without verifying them. When all
+	 * backends are in "inprogress-off" the next transition to "off" can be
+	 * performed, after which all data checksum processing is disabled.
+	 */
+	if (XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_VERSION ||
+		XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_ON)
 	{
 		SpinLockRelease(&XLogCtl->info_lck);
 
diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index ea102086144..e62a36b0af5 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -235,7 +235,7 @@ typedef struct ChecksumBarrierCondition
 	int			to;
 } ChecksumBarrierCondition;
 
-static const ChecksumBarrierCondition checksum_barriers[6] =
+static const ChecksumBarrierCondition checksum_barriers[7] =
 {
 	/*
 	 * Disabling checksums: If checksums are currently enabled, disabling must
@@ -261,6 +261,12 @@ static const ChecksumBarrierCondition checksum_barriers[6] =
 	 * checksums, we can go straight back to 'on'
 	 */
 	{PG_DATA_CHECKSUM_INPROGRESS_OFF, PG_DATA_CHECKSUM_VERSION},
+
+	/*
+	 * If checksums are being enabled when launcher_exit is executed, state
+	 * is set to off since we cannot reach on at that point.
+	 */
+	{PG_DATA_CHECKSUM_INPROGRESS_ON, PG_DATA_CHECKSUM_INPROGRESS_OFF},
 };
 
 /*
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] 0002-Test-improvements-for-online-checksums.patch (7.3K, ../[email protected]/8-0002-Test-improvements-for-online-checksums.patch)
  download | inline diff:
From a24b6fc17b633514de2ca5064c65029910be06ac Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:44:09 +0200
Subject: [PATCH 2/8] Test improvements for online checksums

This includes a number of smaller fixups to the onine checksums test
module which were found during postcommit review and stabilization
work.

 * Fix scope increase for PG_TEST_EXTRA: The online checksums tests
   have two levels of PG_TEST_EXTRA, checksum and checksums_extended
   for extra test runs and test runs with increased randomization.
   The logic for increasing the number of test iterations was however
   backwards.
 * Change stopmode for PITR test: The pitr suite used immediate stop
   mode which caused problems on slower machines where the sigquit
   would interrupt archive commands leaving partial WAL files behind.
   This would then prevent restart.  Fix by using fast mode which is
   the appropriate mode for the test at hand.  Also increase timeouts
   to help slower test systems since an expired timeout will incur
   the same effect as an immediate standby with a partial WAL left
   behind.  This issue was observed when running the test suites on
   a Raspberry Pi 4 machine.
 * Improve logging: The test suite for data checksums use a set of
   helper functions in a Perl module to avoid repeating code, this
   makes sure that the helper functions do a better job of logging
   their test output to make debug easier.
 * Remove unused code: wait_for_cluster_crash was used during the
   development of online checksums but was never used in any test
   which shipped, so remove the function.
 * Standby fixes: Ensure no vacuum on pgbench init on standby with
   -n to avoid bogus error message in the log, and enable
   hot_standby_feedback to prevent queries from getting cancelled
   due to recovery on slower systems.

Author: Daniel Gustafsson <[email protected]>
Author: Tomas Vondra <[email protected]>
Discussion: https://postgr.es/m/xxx
---
 .../test_checksums/t/007_pgbench_standby.pl   | 12 +++--
 src/test/modules/test_checksums/t/008_pitr.pl |  5 +-
 .../test_checksums/t/DataChecksums/Utils.pm   | 53 +++----------------
 3 files changed, 20 insertions(+), 50 deletions(-)

diff --git a/src/test/modules/test_checksums/t/007_pgbench_standby.pl b/src/test/modules/test_checksums/t/007_pgbench_standby.pl
index f3611e7ce25..0b3996f1d69 100644
--- a/src/test/modules/test_checksums/t/007_pgbench_standby.pl
+++ b/src/test/modules/test_checksums/t/007_pgbench_standby.pl
@@ -49,8 +49,8 @@ my $node_standby_loglocation = 0;
 # of tests performed and the wall time taken is non-deterministic as the test
 # performs a lot of randomized actions, but 5 iterations will be a long test
 # run regardless.
-my $TEST_ITERATIONS = 5;
-$TEST_ITERATIONS = 1 if ($extended);
+my $TEST_ITERATIONS = 1;
+$TEST_ITERATIONS = 5 if ($extended);
 
 # Variables which record the current state of the cluster
 my $data_checksum_state = 'off';
@@ -83,6 +83,7 @@ sub background_pgbench
 	push(@cmd, '-C') if ($extended && cointoss());
 	# If we run on a standby it needs to be a read-only benchmark
 	push(@cmd, '-S') if ($standby);
+	push(@cmd, '-n') if ($standby);
 	# Finally add the database name to use
 	push(@cmd, 'postgres');
 
@@ -146,8 +147,10 @@ sub flip_data_checksums
 			  . "FROM pg_catalog.pg_settings "
 			  . "WHERE name = 'data_checksums';");
 
-		is(($result eq 'inprogress-on' || $result eq 'on'),
-			1, 'ensure checksums are on, or in progress, on standby_1');
+		is( ($result eq 'inprogress-on' || $result eq 'on'),
+			1,
+			'ensure checksums are on, or in progress, on standby_1, got: '
+			  . $result);
 
 		# Wait for checksums enabled on the primary and standby
 		wait_for_checksum_state($node_primary, 'on');
@@ -210,6 +213,7 @@ $node_primary->append_conf(
 	qq[
 max_connections = 30
 log_statement = none
+hot_standby_feedback = on
 ]);
 $node_primary->start;
 $node_primary->safe_psql('postgres', 'CREATE EXTENSION test_checksums;');
diff --git a/src/test/modules/test_checksums/t/008_pitr.pl b/src/test/modules/test_checksums/t/008_pitr.pl
index e8cb2b0ed96..1f8176686fd 100644
--- a/src/test/modules/test_checksums/t/008_pitr.pl
+++ b/src/test/modules/test_checksums/t/008_pitr.pl
@@ -124,11 +124,14 @@ $node_primary->init(
 	has_archiving => 1,
 	allows_streaming => 1,
 	no_data_checksums => 1);
+my $timeout_unit = 's';
 $node_primary->append_conf(
 	'postgresql.conf',
 	qq[
 max_connections = 100
 log_statement = none
+wal_sender_timeout = $PostgreSQL::Test::Utils::timeout_default$timeout_unit
+wal_receiver_timeout = $PostgreSQL::Test::Utils::timeout_default$timeout_unit
 ]);
 $node_primary->start;
 
@@ -154,7 +157,7 @@ my ($pre_lsn, $post_lsn) = flip_data_checksums();
 $node_primary->safe_psql('postgres', "UPDATE t SET a = a + 1;");
 $node_primary->safe_psql('postgres', "SELECT pg_create_restore_point('a');");
 $node_primary->safe_psql('postgres', "UPDATE t SET a = a + 1;");
-$node_primary->stop('immediate');
+$node_primary->stop('fast');
 
 my $node_pitr = PostgreSQL::Test::Cluster->new('pitr_backup');
 $node_pitr->init_from_backup(
diff --git a/src/test/modules/test_checksums/t/DataChecksums/Utils.pm b/src/test/modules/test_checksums/t/DataChecksums/Utils.pm
index fb704623a60..cb78dd6ecfb 100644
--- a/src/test/modules/test_checksums/t/DataChecksums/Utils.pm
+++ b/src/test/modules/test_checksums/t/DataChecksums/Utils.pm
@@ -43,7 +43,6 @@ our @EXPORT = qw(
   stopmode
   test_checksum_state
   wait_for_checksum_state
-  wait_for_cluster_crash
 );
 
 =pod
@@ -67,7 +66,10 @@ sub test_checksum_state
 	my $result = $postgresnode->safe_psql('postgres',
 		"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';"
 	);
-	is($result, $state, 'ensure checksums are set to ' . $state);
+	is($result, $state,
+			'ensure checksums are set to '
+		  . $state . ' on '
+		  . $postgresnode->name());
 	return $result eq $state;
 }
 
@@ -89,52 +91,13 @@ sub wait_for_checksum_state
 		'postgres',
 		"SELECT setting FROM pg_catalog.pg_settings WHERE name = 'data_checksums';",
 		$state);
-	is($res, 1, 'ensure data checksums are transitioned to ' . $state);
+	is($res, 1,
+			'ensure data checksums are transitioned to '
+		  . $state . ' on '
+		  . $postgresnode->name());
 	return $res == 1;
 }
 
-=item wait_for_cluster_crash(node, params)
-
-Repeatedly test if the cluster running at B<node> responds to connections
-and return when it no longer does so, or when it times out.  Processing will
-run for $PostgreSQL::Test::Utils::timeout_default seconds unless a timeout
-value is specified as a parameter.  Returns True if the cluster crashed, else
-False if the process timed out.
-
-=over
-
-=item timeout
-
-Approximate number of seconds to wait for cluster to crash, default is
-$PostgreSQL::Test::Utils::timeout_default.  There are no real-time guarantees
-that the total process time won't exceed the timeout.
-
-=back
-
-=cut
-
-sub wait_for_cluster_crash
-{
-	my $postgresnode = shift;
-	my %params = @_;
-	my $crash = 0;
-
-	$params{timeout} = $PostgreSQL::Test::Utils::timeout_default
-	  unless (defined($params{timeout}));
-
-	for (my $naps = 0; $naps < $params{timeout}; $naps++)
-	{
-		if (!$postgresnode->is_alive)
-		{
-			$crash = 1;
-			last;
-		}
-		sleep(1);
-	}
-
-	return $crash == 1;
-}
-
 =item enable_data_checksums($node, %params)
 
 Function for enabling data checksums in the cluster running at B<node>.
-- 
2.39.3 (Apple Git-146)



  [application/octet-stream] 0001-Prevent-pg_enable-disable_data_checksums-on-standby.patch (1.5K, ../[email protected]/9-0001-Prevent-pg_enable-disable_data_checksums-on-standby.patch)
  download | inline diff:
From 96f0fc24add90a7c0af0e3700c24b835f4497a7b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <[email protected]>
Date: Thu, 23 Apr 2026 10:43:58 +0200
Subject: [PATCH 1/8] Prevent pg_enable/disable_data_checksums() on standby

These functions missed a RecoveryInProgress() check, allowing them to
be called on a hot standby.  Enabling, or disabling, checksums on the
standby only would cause the cluster to get out of sync and replaying
checksum transitions to fail.

Author: Satyanarayana Narlapuram <[email protected]>
Discussion: https://postgr.es/m/CAHg+QDfRk4-S7DMmdbXJnQ-xF=sUpMAKuh8b83ObLqYVKx5QLA@mail.gmail.com
---
 src/backend/postmaster/datachecksum_state.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 5556a9ca893..ea102086144 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -487,6 +487,8 @@ AbsorbDataChecksumsBarrier(ProcSignalBarrierType barrier)
 Datum
 disable_data_checksums(PG_FUNCTION_ARGS)
 {
+	PreventCommandDuringRecovery("pg_disable_data_checksums()");
+
 	if (!superuser())
 		ereport(ERROR,
 				errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -507,6 +509,8 @@ enable_data_checksums(PG_FUNCTION_ARGS)
 	int			cost_delay = PG_GETARG_INT32(0);
 	int			cost_limit = PG_GETARG_INT32(1);
 
+	PreventCommandDuringRecovery("pg_enable_data_checksums()");
+
 	if (!superuser())
 		ereport(ERROR,
 				errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-- 
2.39.3 (Apple Git-146)



  [text/plain] POC_test.diff.txt (30.1K, ../[email protected]/10-POC_test.diff.txt)
  download | inline diff:
commit f502fa990e877c6e4d1d03518e524519d8e88806
Author: Tomas Vondra <[email protected]>
Date:   Wed Apr 15 12:50:06 2026 +0200

    POC: Test checksum state transitions using step through injection points

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index be92b6af20f..5b481784543 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4762,6 +4762,9 @@ SetDataChecksumsOnInProgress(void)
 	uint32	data_checksum_version;
 
 	elog(LOG, "SetDataChecksumsOnInProgress / start");
+
+	INJECTION_POINT("datachecksums-enable-inprogress-checksums-delay", NULL);
+
 	/*
 	 * The state transition is performed in a critical section with
 	 * checkpoints held off to provide crash safety.
@@ -4782,6 +4785,8 @@ SetDataChecksumsOnInProgress(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
+	INJECTION_POINT("datachecksums-enable-inprogress-checksums-after-xlogctl", NULL);
+
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 
 	elog(LOG, "SetDataChecksumsOnInProgress ControlFile->data_checksum_version  %u => %u",
@@ -4791,6 +4796,8 @@ SetDataChecksumsOnInProgress(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	INJECTION_POINT("datachecksums-enable-inprogress-checksums-after-controlfile", NULL);
+
 	elog(LOG, "SetDataChecksumsOnInProgress / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_ON)");
 	EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_ON);
 
@@ -4862,6 +4869,8 @@ SetDataChecksumsOn(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
+	INJECTION_POINT("datachecksums-enable-checksums-after-xlogctl", NULL);
+
 	/*
 	 * Update the controlfile before waiting since if we have an immediate
 	 * shutdown while waiting we want to come back up with checksums enabled.
@@ -4875,9 +4884,13 @@ SetDataChecksumsOn(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	INJECTION_POINT("datachecksums-enable-checksums-after-controlfile", NULL);
+
 	elog(LOG, "SetDataChecksumsOn / RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST)");
 	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 
+	INJECTION_POINT("datachecksums-enable-checksums-after-checkpoint", NULL);
+
 	elog(LOG, "SetDataChecksumsOn / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_VERSION)");
 	EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_VERSION);
 
@@ -4924,6 +4937,8 @@ SetDataChecksumsOff(void)
 	{
 		SpinLockRelease(&XLogCtl->info_lck);
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-delay", NULL);
+
 		START_CRIT_SECTION();
 		MyProc->delayChkptFlags |= DELAY_CHKPT_START;
 
@@ -4934,24 +4949,30 @@ SetDataChecksumsOff(void)
 		XLogCtl->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_OFF;
 		SpinLockRelease(&XLogCtl->info_lck);
 
-		elog(LOG, "SetDataChecksumsOff / XLogCtl->data_checksum_version %u = %u",
+		elog(LOG, "SetDataChecksumsOff / XLogCtl->data_checksum_version %u => %u",
 			 data_checksum_version, PG_DATA_CHECKSUM_INPROGRESS_OFF);
 
 		MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 		END_CRIT_SECTION();
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-after-xlogctl", NULL);
+
 		LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 
-		elog(LOG, "SetDataChecksumsOff / ControlFile->data_checksum_version %u = %u",
+		elog(LOG, "SetDataChecksumsOff / ControlFile->data_checksum_version %u => %u",
 			 ControlFile->data_checksum_version, PG_DATA_CHECKSUM_INPROGRESS_OFF);
 
 		ControlFile->data_checksum_version = PG_DATA_CHECKSUM_INPROGRESS_OFF;
 		UpdateControlFile();
 		LWLockRelease(ControlFileLock);
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-after-controlfile", NULL);
+
 		elog(LOG, "SetDataChecksumsOff / RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST)");
 		RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 
+		INJECTION_POINT("datachecksums-disable-inprogress-checksums-after-checkpoint", NULL);
+
 		elog(LOG, "SetDataChecksumsOff / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_OFF)");
 		EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_INPROGRESS_OFF);
 
@@ -4971,6 +4992,8 @@ SetDataChecksumsOff(void)
 		SpinLockRelease(&XLogCtl->info_lck);
 	}
 
+	INJECTION_POINT("datachecksums-disable-checksums-delay", NULL);
+
 	START_CRIT_SECTION();
 	/* Ensure that we don't incur a checkpoint during disabling checksums */
 	MyProc->delayChkptFlags |= DELAY_CHKPT_START;
@@ -4988,6 +5011,8 @@ SetDataChecksumsOff(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
+	INJECTION_POINT("datachecksums-disable-checksums-after-xlogctl", NULL);
+
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 
 	elog(LOG, "SetDataChecksumsOff / ControlFile->data_checksum_version %u => %u",
@@ -4997,9 +5022,13 @@ SetDataChecksumsOff(void)
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
+	INJECTION_POINT("datachecksums-disable-checksums-after-controlfile", NULL);
+
 	elog(LOG, "SetDataChecksumsOff / RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST)");
 	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
 
+	INJECTION_POINT("datachecksums-disable-checksums-after-checkpoint", NULL);
+
 	elog(LOG, "SetDataChecksumsOff / EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_OFF)");
 	EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_OFF);
 
diff --git a/src/test/modules/test_checksums/t/010_injection_2.pl b/src/test/modules/test_checksums/t/010_injection_2.pl
new file mode 100644
index 00000000000..a8ae9ffd151
--- /dev/null
+++ b/src/test/modules/test_checksums/t/010_injection_2.pl
@@ -0,0 +1,209 @@
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test suite for testing enabling data checksums in an online cluster with
+# injection point tests injecting failures into the processing
+
+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;
+
+# This test suite is expensive, or very expensive, to execute.  There are two
+# PG_TEST_EXTRA options for running it, "checksum" for a pared-down test suite
+# an "checksum_extended" for the full suite.  The full suite can run for hours
+# on slow or constrained systems.
+my $extended = undef;
+if ($ENV{PG_TEST_EXTRA})
+{
+	$extended = 1 if ($ENV{PG_TEST_EXTRA} =~ /\bchecksum_extended\b/);
+	plan skip_all => 'Expensive data checksums test disabled'
+	  unless ($ENV{PG_TEST_EXTRA} =~ /\bchecksum(_extended)?\b/);
+}
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# ---------------------------------------------------------------------------
+# Test cluster setup
+#
+
+# Initiate testcluster
+my $node = PostgreSQL::Test::Cluster->new('injection_node');
+$node->init(no_data_checksums => 1);
+$node->start;
+
+# Set up test environment
+$node->safe_psql('postgres', 'CREATE EXTENSION test_checksums;');
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+my $pgbench = undef;
+my $scalefactor = ($extended ? 10 : 1);
+my $node_loglocation = 0;
+
+$node->command_ok(
+	[
+		'pgbench', '-p', $node->port, '-i',
+		'-s', $scalefactor, '-q', 'postgres'
+	]);
+
+# Start a pgbench run in the background against the server specified via the
+# port passed as parameter.
+sub background_rw_pgbench
+{
+	my $port = shift;
+
+	# If a previous pgbench is still running, start by shutting it down.
+	$pgbench->finish if $pgbench;
+
+	my $clients = 1;
+	my $runtime = 2;
+
+	if ($extended)
+	{
+		# Randomize the number of pgbench clients a bit (range 1-16)
+		$clients = 1 + int(rand(15));
+		$runtime = 600;
+	}
+	my @cmd = ('pgbench', '-p', $port, '-T', $runtime, '-c', $clients);
+
+	# Randomize whether we spawn connections or not
+	push(@cmd, '-C') if ($extended && cointoss);
+	# Finally add the database name to use
+	push(@cmd, 'postgres');
+
+	$pgbench = IPC::Run::start(
+		\@cmd,
+		'<' => '/dev/null',
+		'>' => '/dev/null',
+		'2>' => '/dev/null',
+		IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default));
+}
+
+# Test checksum transition. The function has these arguments:
+#
+# - start checksum state (enabled/disabled)
+# - first - first checksum change
+# - second - second checksum change
+# - point - injection point the first change should wait on
+# - final - expected checksum state at the end
+#
+# The test puts the instance into the initial checksum state, triggers two
+# checksum changes, and verifies the final state is as expected. The first
+# state change is paused on a selected injection point, and unpaused after
+# the second change gets initiated.
+#
+# The injection point is triggered only by the datachecksum launcher, and
+# there can be only one such process. So there's no risk of hitting the
+# injection point by both changes.
+sub test_checksum_transition
+{
+	my ($start, $first, $second, $point, $final) = @_;
+
+	$node->safe_psql('postgres',
+		"SELECT '========== " . $start . " / " . $first . " / " . $second . " / " . $point . " / " . $final . " =========='");
+
+	note($start . " / " . $first . " / " . $second . " / " . $point . " / " . $final);
+
+	note('changing checksums into initial state: ' . $start);
+
+	enable_data_checksums($node, wait => 'on') if ($start eq 'enabled');
+	disable_data_checksums($node, wait => 'off') if ($start eq 'disabled');
+
+	note('attaching injection point: ' . $point);
+	$node->safe_psql('postgres',
+		"SELECT injection_points_attach('" . $point . "','wait');"
+	);
+
+	note("triggering first checksum change: " . $first);
+
+	enable_data_checksums($node) if ($first eq 'enable');
+	disable_data_checksums($node) if ($first eq 'disable');
+
+	note("waiting for the injection point to be hit");
+	$node->poll_query_until(
+		'postgres',
+		"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point . "'",
+		'1');
+
+	note("triggering second checksum change: " . $second);
+
+	enable_data_checksums($node) if ($second eq 'enable');
+	disable_data_checksums($node) if ($second eq 'disable');
+
+	note("waking and detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_wakeup('" . $point . "');");
+
+	note("detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_detach('" . $point . "');");
+
+	note('wait for the checksum launcher to exit');
+	$node->poll_query_until('postgres',
+			"SELECT count(*) = 0 "
+		  . "FROM pg_catalog.pg_stat_activity "
+		  . "WHERE backend_type = 'datachecksum launcher';");
+
+	test_checksum_state($node, $final);
+
+	# Since the log isn't being written to now, parse the log and check
+	# for instances of checksum verification failures.
+	my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile,
+		$node_loglocation);
+	unlike(
+		$log,
+		qr/page verification failed,.+\d$/,
+		"no checksum validation errors in primary log (during WAL recovery)"
+	);
+	$node_loglocation = -s $node->logfile;
+}
+
+# Start the test suite with pgbench running.
+background_rw_pgbench($node->port);
+
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-inprogress-checksums-delay', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-delay', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-after-xlogctl', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-after-controlfile', 'off');
+test_checksum_transition('disabled', 'enable', 'disable', 'datachecksums-enable-checksums-after-checkpoint', 'off');
+
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-inprogress-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'enable', 'datachecksums-enable-checksums-after-checkpoint', 'on');
+
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'disable', 'datachecksums-disable-checksums-after-checkpoint', 'off');
+
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-delay', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-delay', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-after-xlogctl', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-after-controlfile', 'on');
+test_checksum_transition('enabled', 'disable', 'enable', 'datachecksums-disable-checksums-after-checkpoint', 'on');
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_checksums/t/011_injection_checkpoint.pl b/src/test/modules/test_checksums/t/011_injection_checkpoint.pl
new file mode 100644
index 00000000000..ce1553e8ecd
--- /dev/null
+++ b/src/test/modules/test_checksums/t/011_injection_checkpoint.pl
@@ -0,0 +1,196 @@
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test suite for testing enabling data checksums in an online cluster with
+# injection point tests injecting failures into the processing
+
+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;
+
+# This test suite is expensive, or very expensive, to execute.  There are two
+# PG_TEST_EXTRA options for running it, "checksum" for a pared-down test suite
+# an "checksum_extended" for the full suite.  The full suite can run for hours
+# on slow or constrained systems.
+my $extended = undef;
+if ($ENV{PG_TEST_EXTRA})
+{
+	$extended = 1 if ($ENV{PG_TEST_EXTRA} =~ /\bchecksum_extended\b/);
+	plan skip_all => 'Expensive data checksums test disabled'
+	  unless ($ENV{PG_TEST_EXTRA} =~ /\bchecksum(_extended)?\b/);
+}
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# ---------------------------------------------------------------------------
+# Test cluster setup
+#
+
+# Initiate testcluster
+my $node = PostgreSQL::Test::Cluster->new('injection_node');
+$node->init(no_data_checksums => 1);
+$node->start;
+
+# Set up test environment
+$node->safe_psql('postgres', 'CREATE EXTENSION test_checksums;');
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+my $pgbench = undef;
+my $scalefactor = ($extended ? 10 : 1);
+my $node_loglocation = 0;
+
+$node->command_ok(
+	[
+		'pgbench', '-p', $node->port, '-i',
+		'-s', $scalefactor, '-q', 'postgres'
+	]);
+
+# Start a pgbench run in the background against the server specified via the
+# port passed as parameter.
+sub background_rw_pgbench
+{
+	my $port = shift;
+
+	# If a previous pgbench is still running, start by shutting it down.
+	$pgbench->finish if $pgbench;
+
+	my $clients = 1;
+	my $runtime = 2;
+
+	if ($extended)
+	{
+		# Randomize the number of pgbench clients a bit (range 1-16)
+		$clients = 1 + int(rand(15));
+		$runtime = 600;
+	}
+	my @cmd = ('pgbench', '-p', $port, '-T', $runtime, '-c', $clients);
+
+	# Randomize whether we spawn connections or not
+	push(@cmd, '-C') if ($extended && cointoss);
+	# Finally add the database name to use
+	push(@cmd, 'postgres');
+
+	$pgbench = IPC::Run::start(
+		\@cmd,
+		'<' => '/dev/null',
+		'>' => '/dev/null',
+		'2>' => '/dev/null',
+		IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default));
+}
+
+# Test checksum transition concurrent with a checkpoint.
+#
+# The function has these arguments:
+#
+# - start checksum state (enabled/disabled)
+# - change - checksum change to initiate
+# - point - injection point the first change should wait on
+# - final - expected checksum state at the end
+#
+# The test puts the instance into the initial checksum state, triggers a
+# checksum change concurrent with a checkpoint, and verifies the final state
+# is as expected. The state change is paused on a selected injection
+# point, and unpaused after performing a checkpoint.
+#
+# Finally, the instance is restarted (in either fast ot immediate mode),
+# the final checksum state is validated against the expected value, and
+# the server log is checked for checksum failures.
+sub test_checksum_transition
+{
+	my ($start, $change, $point, $final) = @_;
+
+	# Start the test suite with pgbench running.
+	background_rw_pgbench($node->port);
+
+	$node->safe_psql('postgres',
+		"SELECT '========== " . $start . " / " . $change . " / " . $point . " / " . $final . " =========='");
+
+	note($start . " / " . $change . " / " . $point . " / " . $final);
+
+	note('changing checksums into initial state: ' . $start);
+
+	enable_data_checksums($node, wait => 'on') if ($start eq 'enabled');
+	disable_data_checksums($node, wait => 'off') if ($start eq 'disabled');
+
+	note('attaching injection point: ' . $point);
+	$node->safe_psql('postgres',
+		"SELECT injection_points_attach('" . $point . "','wait');"
+	);
+
+	note("triggering checksum change: " . $change);
+
+	enable_data_checksums($node) if ($change eq 'enable');
+	disable_data_checksums($node) if ($change eq 'disable');
+
+	note("waiting for the injection point to be hit");
+	$node->poll_query_until(
+		'postgres',
+		"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point . "'",
+		'1');
+
+	note('checkpoint');
+	$node->safe_psql('postgres', "CHECKPOINT");
+
+	note("waking and detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_wakeup('" . $point . "');");
+
+	note("detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_detach('" . $point . "');");
+
+	note('wait for the checksum launcher to exit');
+	$node->poll_query_until('postgres',
+			"SELECT count(*) = 0 "
+		  . "FROM pg_catalog.pg_stat_activity "
+		  . "WHERE backend_type = 'datachecksum launcher';");
+
+	test_checksum_state($node, $final);
+
+	$node->stop(stopmode());
+	$node->start;
+
+	test_checksum_state($node, $final);
+
+	# Since the log isn't being written to now, parse the log and check
+	# for instances of checksum verification failures.
+	my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile,
+		$node_loglocation);
+	unlike(
+		$log,
+		qr/page verification failed,.+\d$/,
+		"no checksum validation errors in primary log (during WAL recovery)"
+	);
+	$node_loglocation = -s $node->logfile;
+}
+
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-delay', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-checkpoint', 'on');
+
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-checkpoint', 'off');
+
+$node->stop;
+done_testing();
diff --git a/src/test/modules/test_checksums/t/012_injection_checkpoint_crash.pl b/src/test/modules/test_checksums/t/012_injection_checkpoint_crash.pl
new file mode 100644
index 00000000000..98e9c805c41
--- /dev/null
+++ b/src/test/modules/test_checksums/t/012_injection_checkpoint_crash.pl
@@ -0,0 +1,214 @@
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test suite for testing enabling data checksums in an online cluster with
+# injection point tests injecting failures into the processing
+
+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;
+
+# This test suite is expensive, or very expensive, to execute.  There are two
+# PG_TEST_EXTRA options for running it, "checksum" for a pared-down test suite
+# an "checksum_extended" for the full suite.  The full suite can run for hours
+# on slow or constrained systems.
+my $extended = undef;
+if ($ENV{PG_TEST_EXTRA})
+{
+	$extended = 1 if ($ENV{PG_TEST_EXTRA} =~ /\bchecksum_extended\b/);
+	plan skip_all => 'Expensive data checksums test disabled'
+	  unless ($ENV{PG_TEST_EXTRA} =~ /\bchecksum(_extended)?\b/);
+}
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# ---------------------------------------------------------------------------
+# Test cluster setup
+#
+
+# Initiate testcluster
+my $node = PostgreSQL::Test::Cluster->new('injection_node');
+$node->init(no_data_checksums => 1);
+$node->start;
+
+# Set up test environment
+$node->safe_psql('postgres', 'CREATE EXTENSION test_checksums;');
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+my $pgbench = undef;
+my $scalefactor = ($extended ? 10 : 1);
+my $node_loglocation = 0;
+
+$node->command_ok(
+	[
+		'pgbench', '-p', $node->port, '-i',
+		'-s', $scalefactor, '-q', 'postgres'
+	]);
+
+# Start a pgbench run in the background against the server specified via the
+# port passed as parameter.
+sub background_rw_pgbench
+{
+	my $port = shift;
+
+	# If a previous pgbench is still running, start by shutting it down.
+	$pgbench->finish if $pgbench;
+
+	my $clients = 1;
+	my $runtime = 2;
+
+	if ($extended)
+	{
+		# Randomize the number of pgbench clients a bit (range 1-16)
+		$clients = 1 + int(rand(15));
+		$runtime = 600;
+	}
+	my @cmd = ('pgbench', '-p', $port, '-T', $runtime, '-c', $clients);
+
+	# Randomize whether we spawn connections or not
+	push(@cmd, '-C') if ($extended && cointoss);
+	# Finally add the database name to use
+	push(@cmd, 'postgres');
+
+	$pgbench = IPC::Run::start(
+		\@cmd,
+		'<' => '/dev/null',
+		'>' => '/dev/null',
+		'2>' => '/dev/null',
+		IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default));
+}
+
+# Test checksum transition concurrent with a checkpoint.
+#
+# The function has these arguments:
+#
+# - start checksum state (enabled/disabled)
+# - change - checksum change to initiate
+# - point1 - injection point before checkpoint
+# - point2 - injection point after checkpoint
+# - final - expected checksum state at the end
+#
+# The test puts the instance into the initial checksum state, triggers a
+# checksum change that pauses on a selected injection point. Then performs
+# a checkpoint, unpauses the change so that it proceeds to a second
+# injection point.
+#
+# Then the instance is restarted in immediate mode to simulate failure,
+# and the final checksum state is validated against the expected value.
+# The server log is checked for checksum failures.
+sub test_checksum_transition
+{
+	my ($start, $change, $point1, $point2, $final) = @_;
+
+	# Start the test suite with pgbench running.
+	background_rw_pgbench($node->port);
+
+	$node->safe_psql('postgres',
+		"SELECT '========== " . $start . " / " . $change . " / " . $point1 . " / " . $final . " =========='");
+
+	note($start . " / " . $change . " / " . $point1 . " / " . $final);
+
+	note('changing checksums into initial state: ' . $start);
+
+	enable_data_checksums($node, wait => 'on') if ($start eq 'enabled');
+	disable_data_checksums($node, wait => 'off') if ($start eq 'disabled');
+
+	note('attaching injection point: ' . $point1);
+	$node->safe_psql('postgres',
+		"SELECT injection_points_attach('" . $point1 . "','wait');"
+	);
+
+	if (defined($point2))
+	{
+		note('attaching injection point: ' . $point2);
+		$node->safe_psql('postgres',
+			"SELECT injection_points_attach('" . $point2 . "','wait');"
+		);
+	}
+
+	note("triggering checksum change: " . $change);
+
+	enable_data_checksums($node) if ($change eq 'enable');
+	disable_data_checksums($node) if ($change eq 'disable');
+
+	note("waiting for the injection point to be hit");
+	$node->poll_query_until(
+		'postgres',
+		"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point1 . "'",
+		'1');
+
+	note('checkpoint');
+	$node->safe_psql('postgres', "CHECKPOINT");
+
+	note("waking and detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_wakeup('" . $point1 . "');");
+
+	note("detaching injection point");
+	$node->safe_psql('postgres',
+		"SELECT injection_points_detach('" . $point1 . "');");
+
+	if (defined($point2))
+	{
+		note("waiting for the injection point to be hit");
+		$node->poll_query_until(
+			'postgres',
+			"SELECT COUNT(*) FROM pg_catalog.pg_stat_activity WHERE wait_event = '" . $point2 . "'",
+			'1');
+	}
+	else
+	{
+		note('wait for the checksum launcher to exit');
+		$node->poll_query_until('postgres',
+				"SELECT count(*) = 0 "
+			  . "FROM pg_catalog.pg_stat_activity "
+			  . "WHERE backend_type = 'datachecksum launcher';");
+	}
+
+	$node->stop('immediate');
+	$node->start;
+
+	test_checksum_state($node, $final);
+
+	# Since the log isn't being written to now, parse the log and check
+	# for instances of checksum verification failures.
+	my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile,
+		$node_loglocation);
+	unlike(
+		$log,
+		qr/page verification failed,.+\d$/,
+		"no checksum validation errors in primary log (during WAL recovery)"
+	);
+	$node_loglocation = -s $node->logfile;
+}
+
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-delay', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-xlogctl', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-inprogress-checksums-after-controlfile', 'datachecksums-enable-checksums-delay', 'off');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-delay', 'datachecksums-enable-checksums-after-xlogctl', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-xlogctl', 'datachecksums-enable-checksums-after-controlfile', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-controlfile', 'datachecksums-enable-checksums-after-checkpoint', 'on');
+test_checksum_transition('disabled', 'enable', 'datachecksums-enable-checksums-after-checkpoint', undef, 'on');
+
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-delay', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-xlogctl', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-controlfile', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-inprogress-checksums-after-checkpoint', 'datachecksums-disable-checksums-delay', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-delay', 'datachecksums-disable-checksums-after-xlogctl', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-xlogctl', 'datachecksums-disable-checksums-after-controlfile', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-controlfile', 'datachecksums-disable-checksums-after-checkpoint', 'off');
+test_checksum_transition('enabled', 'disable', 'datachecksums-disable-checksums-after-checkpoint', undef, 'off');
+
+$node->stop;
+done_testing();


view thread (60+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: Changing the state of data checksums in a running cluster
  In-Reply-To: <[email protected]>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox