@@ -733,14 +733,126 @@ sub wait_until_vacuum_can_remove
733733
734734# #################################################
735735# Recovery conflict: Invalidate conflicting slots, including in-use slots
736- # Scenario 6: incorrect wal_level on primary.
736+ # Scenario 6: Race condition between slot invalidation and active process
737+ # termination.
738+ # #################################################
739+ SKIP:
740+ {
741+ skip " Injection points not supported by this build" , 1
742+ if ($ENV {enable_injection_points } ne ' yes' );
743+
744+ # Get the position to search from in the standby logfile
745+ $logstart = -s $node_standby -> logfile;
746+
747+ # Drop the slots, re-create them, change hot_standby_feedback,
748+ # check xmin and catalog_xmin values, make slot active and reset stat.
749+ reactive_slots_change_hfs_and_wait_for_xmins(' pruning_' , ' injection_' , 0,
750+ 1);
751+
752+ # Create the injection_points extension.
753+ $node_primary -> safe_psql(' testdb' , ' CREATE EXTENSION injection_points;' );
754+
755+ # Wait until the extension has been created on the standby.
756+ $node_primary -> wait_for_replay_catchup($node_standby );
757+
758+ # Attach the injection point.
759+ $node_standby -> safe_psql(' testdb' ,
760+ " SELECT injection_points_attach('terminate-process-holding-slot', 'wait');"
761+ );
762+
763+ # Trigger a conflict and insert an XLOG_RUNNING_XACTS before triggering
764+ # the vacuum.
765+ $node_primary -> safe_psql(
766+ ' testdb' , qq[ CREATE TABLE injection_test(x integer);
767+ DROP TABLE injection_test;
768+ SELECT pg_log_standby_snapshot();] );
769+
770+ # Now launch the vacuum.
771+ wait_until_vacuum_can_remove(' ' ,
772+ ' CREATE TABLE injection_test2(x integer);' , ' pg_class' );
773+
774+ # Wait until the startup process hits the injection point by looking at
775+ # pg_stat_activity; the termination LOG message has been emitted and
776+ # the process has been killed once we wait at the injection point.
777+ $node_standby -> wait_for_event(' startup' ,
778+ ' terminate-process-holding-slot' );
779+
780+ # Note: $node_primary->wait_for_replay_catchup($node_standby) would be
781+ # hanging here due to the injection point, so check the log instead.
782+
783+ ok( $node_standby -> log_contains(
784+ " terminating process .* to release replication slot \" injection_activeslot\" " ,
785+ $logstart ),
786+ " terminating process holding the active slot is logged with injection point"
787+ );
788+
789+ # Extract xid_horizon from the logfile.
790+ my $log_contents = slurp_file($node_standby -> logfile, $logstart );
791+ (my $xid_horizon ) =
792+ $log_contents =~ m / The slot conflicted with xid horizon ([0-9]*)./
793+ or die " could not get xid horizon" ;
794+
795+ # Ensure the slot is not active.
796+ $node_standby -> poll_query_until(' testdb' ,
797+ " SELECT active_pid is NULL from pg_catalog.pg_replication_slots where slot_name = 'injection_activeslot'"
798+ ) or die " injection_activeslot is still active" ;
799+
800+ # Decode changes from the slot to reach
801+ # LogicalConfirmReceivedLocation().
802+ $node_standby -> safe_psql(' testdb' ,
803+ qq[ SELECT pg_logical_slot_get_changes('injection_activeslot', NULL, NULL);]
804+ );
805+
806+ # Wait until catalog_xmin advances after the xid_horizon. A conflict
807+ # reason has to be reported.
808+ $node_standby -> poll_query_until(' testdb' ,
809+ " SELECT (SELECT catalog_xmin::text::int - $xid_horizon from pg_catalog.pg_replication_slots where slot_name = 'injection_activeslot') > 0"
810+ ) or die " catalog_xmin did not advance" ;
811+
812+ # Get the position to search from in the standby logfile.
813+ $logstart = -s $node_standby -> logfile;
814+
815+ # Wakeup the injection point.
816+ $node_standby -> safe_psql(' testdb' ,
817+ " SELECT injection_points_wakeup('terminate-process-holding-slot');" );
818+
819+ # Wait for the standby to catchup.
820+ $node_primary -> wait_for_replay_catchup($node_standby );
821+
822+ # Check invalidation in the logfile for the active slot.
823+ ok( $node_standby -> log_contains(
824+ " invalidating obsolete replication slot \" injection_activeslot\" " ,
825+ $logstart ),
826+ " activeslot slot invalidation is logged with injection point" );
827+
828+ # Verify conflict_reason is 'rows_removed' in pg_replication_slots.
829+ check_slots_conflict_reason(' injection_' , ' rows_removed' );
830+
831+ # Detach from the injection point
832+ $node_standby -> safe_psql(' testdb' ,
833+ " SELECT injection_points_detach('terminate-process-holding-slot');" );
834+
835+ # Turn hot_standby_feedback back on
836+ change_hot_standby_feedback_and_wait_for_xmins(1, 1);
837+ }
838+
839+ # #################################################
840+ # Recovery conflict: Invalidate conflicting slots, including in-use slots
841+ # Scenario 7: incorrect wal_level on primary.
737842# #################################################
738843
739844# get the position to search from in the standby logfile
740845$logstart = -s $node_standby -> logfile;
741846
742847# drop the logical slots
743- drop_logical_slots(' pruning_' );
848+ if ($ENV {enable_injection_points } ne ' yes' )
849+ {
850+ drop_logical_slots(' pruning_' );
851+ }
852+ else
853+ {
854+ drop_logical_slots(' injection_' );
855+ }
744856
745857# create the logical slots
746858create_logical_slots($node_standby , ' wal_level_' );
0 commit comments