From: Daniel Gustafsson Date: Fri, 29 May 2026 19:26:18 +0000 (+0200) Subject: Fix checksum state transition during promotion X-Git-Tag: REL_19_BETA1~15 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=5fee7cab1b8746c9e1bc25b20158cb074c7a76da;p=thirdparty%2Fpostgresql.git Fix checksum state transition during promotion When a standby is promoted to primary during checksum enabling when the state is inprogress-on, the standby shall revert the state to off since checksums weren't fully enabled at the time of the crash. Consider the following scenario: 1) primary/standby cluster has checksums off 2) primary starts enabling checksums 3) primary moves to inprogress-on 4) standby receives that and moves to inprogress-on too 5) primary crashes 6) standby gets promoted, and does the StartupXLOG thing 7) standby moves from inprogress-on back to off Any processes in the standby need to be informed at step 6 to change state with a procsignalbarrier, else they will stay in inprogress-on while new backends will see the state as off. StartupXLOG failed to emit a procsignalbarrier which caused inconsistent state in the node promoted to primary. Fixed by emitting a procsignalbarrier during promotion, and adding a new test for this scenario. Author: Daniel Gustafsson Reported-by: Tomas Vondra Discussion: https://postgr.es/m/f1281cf3-89a3-4936-9bc5-2a5a6291229f@vondra.me --- diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index fecdf0d4b05..b32f54f8402 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -6610,6 +6610,7 @@ StartupXLOG(void) SetLocalDataChecksumState(XLogCtl->data_checksum_version); SpinLockRelease(&XLogCtl->info_lck); + EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_OFF); ereport(WARNING, errmsg("enabling data checksums was interrupted"), errhint("Data checksum processing must be manually restarted for checksums to be enabled.")); @@ -6621,7 +6622,7 @@ StartupXLOG(void) * checksums and we can move to off instead of prompting the user to * perform any action. */ - if (XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_OFF) + else if (XLogCtl->data_checksum_version == PG_DATA_CHECKSUM_INPROGRESS_OFF) { XLogChecksums(PG_DATA_CHECKSUM_OFF); @@ -6629,6 +6630,8 @@ StartupXLOG(void) XLogCtl->data_checksum_version = PG_DATA_CHECKSUM_OFF; SetLocalDataChecksumState(XLogCtl->data_checksum_version); SpinLockRelease(&XLogCtl->info_lck); + + EmitAndWaitDataChecksumsBarrier(PG_DATA_CHECKSUM_OFF); } /* diff --git a/src/test/modules/test_checksums/t/003_standby_restarts.pl b/src/test/modules/test_checksums/t/003_standby_restarts.pl index 5bbf38ed21c..2ac51344585 100644 --- a/src/test/modules/test_checksums/t/003_standby_restarts.pl +++ b/src/test/modules/test_checksums/t/003_standby_restarts.pl @@ -226,4 +226,62 @@ unlike( qr/page verification failed,.+\d$/m, "no checksum validation errors in standby log"); +# --------------------------------------------------------------------------- +# Test that enforced state transitions during promotion (via StartupXLOG) are +# performed as expected. When the primary crashes during inprogress-on the +# standby should revert to off at promotion. In order to check the transition +# the test will keep an open psql session with the standby during promotion. + +# The cluster is currently broken down from the previous test. Start up the +# primary as primary, disable checksums and create a new standby from that +# state. +$node_standby->clean_node(); +$node_primary->start(); +disable_data_checksums($node_primary, wait => 'off'); + +# Re-create a new streaming standby linking to primary. The replication slot +# name is reused from earlier but a fresh backup is taken +$backup_name = 'my_new_backup'; +$node_primary->backup($backup_name); +$node_standby = PostgreSQL::Test::Cluster->new('standby_restarts_standby'); +$node_standby->init_from_backup($node_primary, $backup_name, + has_streaming => 1); +$node_standby->append_conf( + 'postgresql.conf', qq[ +primary_slot_name = '$slotname' +]); +$node_standby->start; +$node_primary->wait_for_catchup($node_standby, 'replay'); + +# Open a background psql connection on the primary and inject a barrier to +# block progress on to keep the state from advancing past inprogress-on +my $node_primary_bpsql = $node_primary->background_psql('postgres'); +$node_primary_bpsql->query_safe('CREATE TEMPORARY TABLE tt (a integer);'); +# Also open a background psql connection to the standby to make sure we have +# an active backend during promotion. +my $node_standby_bpsql = $node_standby->background_psql('postgres'); + +# Start to enable checksums and wait until both primary and standby have moved +# to the inprogress-on state. Processing will block here as the temporary rel +# barrier will block the primary from finishing. +enable_data_checksums($node_primary, wait => 'inprogress-on'); +$node_primary->wait_for_catchup($node_standby, 'replay'); +test_checksum_state($node_standby, 'inprogress-on'); + +# Crash the primary before checksums are enabled and promote the standby. The +# new primary node will now revert the state of 'off' since checksums weren't +# fully enabled during the crash. +$node_primary->teardown_node(); +$node_standby->promote; +wait_for_checksum_state($node_standby, 'off'); + +# Ensure that the any backend which was active before, and during, promotion +# sees the new state. +$result = $node_standby_bpsql->query_safe("SHOW data_checksums;"); +is($result, 'off', + 'ensure checksums are set to off after promotion during inprogress-on'); + +$node_standby_bpsql->quit; +$node_standby->stop; + done_testing();