From 9395c0584dbe0c6b800ff33198f90adffe547b0e Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Fri, 14 Aug 2026 17:06:19 +0000 Subject: [PATCH v4 2/4] pg_checksums: Refuse interrupted transitions, note change is local An inprogress-on or inprogress-off control file means an online transition was cut short; running the offline tool on top of it mixes two procedures. Refuse it in every mode, with a hint pointing at the start-stop cycle (or, on a standby, at letting replication finish) that resets the state. Also print that the change applies to one data directory only, with standby-aware wording, since in a replication setup the same change must be made on every node. On a primary, inprogress-on cannot survive a graceful stop: the checksums launcher resolves it from its exit cleanup, and a crashed primary is already rejected by the existing "cluster must be shut down" check. inprogress-off can, however: it is set by the backend running pg_disable_data_checksums(), and a fast shutdown arriving between its two barriers leaves it behind in a cleanly shut down control file, where the next start-stop cycle resolves it at end of recovery. A standby can be stopped with either state, since it has no launcher and only carries forward whatever state the last replayed WAL record left it in, with a restartpoint persisting that as-is. The standby is also the deterministic way to reach the new guard, which is why the test coverage uses one; the primary window would need an injection point between the two barriers. The pg_checksums page said the tool still processes all relation files regardless of an interrupted online transition; describe the refusal instead. --- doc/src/sgml/ref/pg_checksums.sgml | 11 +++++----- src/bin/pg_checksums/pg_checksums.c | 21 +++++++++++++++++++ .../test_checksums/t/012_offline_standby.pl | 8 +++---- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/doc/src/sgml/ref/pg_checksums.sgml b/doc/src/sgml/ref/pg_checksums.sgml index bf07da09754..000940e7cb7 100644 --- a/doc/src/sgml/ref/pg_checksums.sgml +++ b/doc/src/sgml/ref/pg_checksums.sgml @@ -49,11 +49,12 @@ PostgreSQL documentation - When enabling checksums with pg_checksums, if - checksums were in the process of being enabled using - when the cluster was shut - down, pg_checksums will still process all - relation files regardless of the progress of online checksum processing. + If checksums were in the process of being enabled or disabled using + when the cluster was + shut down, the control file still records that interrupted state, and + pg_checksums refuses to run in any mode. + Start the cluster and shut it down cleanly to reset the state, then + retry; on a standby, let replication complete the transition first. diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index 3b3ae23f1a6..e9fccbe9f71 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -586,6 +586,21 @@ main(int argc, char *argv[]) ControlFile->state != DB_SHUTDOWNED_IN_RECOVERY) pg_fatal("cluster must be shut down"); + /* + * An inprogress state means an online transition was cut short. A + * standby stopped mid-transition carries either state; a cleanly shut + * down primary can still carry inprogress-off, which a fast shutdown + * during pg_disable_data_checksums() leaves behind, while inprogress-on + * is always resolved by the launcher's exit cleanup. + */ + if (ControlFile->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_ON || + ControlFile->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_OFF) + { + pg_log_error("an online data checksum state transition was interrupted"); + pg_log_error_hint("Start and cleanly shut down the cluster once to reset the data checksum state, then retry. On a standby, let replication complete the transition first."); + exit(1); + } + if (ControlFile->data_checksum_version != PG_DATA_CHECKSUM_VERSION && mode == PG_MODE_CHECK) pg_fatal("data checksums are not enabled in cluster"); @@ -663,6 +678,12 @@ main(int argc, char *argv[]) printf(_("Checksums enabled in cluster\n")); else printf(_("Checksums disabled in cluster\n")); + + printf(_("This change applies to this data directory only.\n")); + if (ControlFile->state == DB_SHUTDOWNED_IN_RECOVERY) + printf(_("This node appears to be a standby; apply the same change to the primary and all other standbys.\n")); + else + printf(_("In a replication setup, apply the same change to every node while all are stopped, before restarting any of them.\n")); } return 0; diff --git a/src/test/modules/test_checksums/t/012_offline_standby.pl b/src/test/modules/test_checksums/t/012_offline_standby.pl index 94c476293b7..33ee6802168 100644 --- a/src/test/modules/test_checksums/t/012_offline_standby.pl +++ b/src/test/modules/test_checksums/t/012_offline_standby.pl @@ -96,10 +96,8 @@ test_checksum_state($standby, 'off'); $standby->stop; command_checks_all( [ 'pg_checksums', '--enable', '-D', $standby->data_dir ], - 0, - [qr/appears to be a standby/], - [], - 'standby-role notice on offline enable'); + 0, [qr/appears to be a standby/], + [], 'standby-role notice on offline enable'); $standby->start; test_checksum_state($standby, 'on'); $primary->wait_for_catchup($standby); @@ -205,7 +203,7 @@ wait_for_checksum_state($primary, 'on'); $primary->wait_for_catchup($standby); wait_for_checksum_state($standby, 'on'); -is( $standby->safe_psql('postgres', "SELECT count(*) FROM t;"), +is($standby->safe_psql('postgres', "SELECT count(*) FROM t;"), '10001', 'standby readable once the transition completes'); $standby->stop; -- 2.39.3 (Apple Git-146)