From 1a87d755258cf96d82c67fca18b3db97980af237 Mon Sep 17 00:00:00 2001
From: "suyu.cmj" <mengjuan.cmj@alibaba-inc.com>
Date: Wed, 12 Jul 2023 04:05:48 +0000
Subject: [PATCH] Reproduce the error that the same 2pc may be added repeatedly

---
 src/backend/access/transam/xlog.c             | 10 +++
 src/backend/postmaster/checkpointer.c         | 51 ++++++++++++++
 src/include/catalog/pg_proc.dat               |  8 +++
 src/include/postmaster/bgwriter.h             |  3 +
 .../037_add_duplicate_2pc_during_recovery.pl  | 68 +++++++++++++++++++
 5 files changed, 140 insertions(+)
 create mode 100644 src/test/recovery/t/037_add_duplicate_2pc_during_recovery.pl

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8b0710abe6..1e91f1e342 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6624,6 +6624,11 @@ CreateCheckPoint(int flags)
 
 	TRACE_POSTGRESQL_CHECKPOINT_START(flags);
 
+	/* Test inject fault */
+	while (IsCheckpointDelayed())
+		pg_usleep(1000000);
+	/* Test inject fault end */
+
 	/*
 	 * Get the other info we need for the checkpoint record.
 	 *
@@ -7039,6 +7044,11 @@ CheckPointGuts(XLogRecPtr checkPointRedo, int flags)
 
 	/* We deliberately delay 2PC checkpointing as long as possible */
 	CheckPointTwoPhase(checkPointRedo);
+
+	/* Test inject fault */
+	if (IsCheckpointPanicInjected())
+		elog(PANIC, "panic is injected during checkpoint");
+	/* Test inject fault end */
 }
 
 /*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index ace9893d95..161f98a1a3 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -122,6 +122,9 @@ typedef struct
 
 	int			ckpt_flags;		/* checkpoint flags, as defined in xlog.h */
 
+	bool		is_ckpt_delayed; /* add for test */
+	bool		is_ckpt_panic_injected; /* add for test */
+
 	ConditionVariable start_cv; /* signaled when ckpt_started advances */
 	ConditionVariable done_cv;	/* signaled when ckpt_done advances */
 
@@ -1351,3 +1354,51 @@ FirstCallSinceLastCheckpoint(void)
 
 	return FirstCall;
 }
+
+bool
+IsCheckpointDelayed(void)
+{
+	bool is_delayed = false;
+
+	SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+	is_delayed = CheckpointerShmem->is_ckpt_delayed;
+	SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+	return is_delayed;
+}
+
+bool
+IsCheckpointPanicInjected(void)
+{
+	bool inject_panic = false;
+
+	SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+	inject_panic = CheckpointerShmem->is_ckpt_panic_injected;
+	SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+	return inject_panic;
+}
+
+Datum
+pg_test_inject_checkpoint_delay(PG_FUNCTION_ARGS)
+{
+	bool set_delay = PG_GETARG_BOOL(0);
+
+	SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+	CheckpointerShmem->is_ckpt_delayed = set_delay;
+	SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+	PG_RETURN_VOID();
+}
+
+Datum
+pg_test_inject_checkpoint_panic(PG_FUNCTION_ARGS)
+{
+	bool inject_panic = PG_GETARG_BOOL(0);
+
+	SpinLockAcquire(&CheckpointerShmem->ckpt_lck);
+	CheckpointerShmem->is_ckpt_panic_injected = inject_panic;
+	SpinLockRelease(&CheckpointerShmem->ckpt_lck);
+
+	PG_RETURN_VOID();
+}
\ No newline at end of file
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..8e3d9c5ea3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12043,4 +12043,12 @@
   proname => 'any_value_transfn', prorettype => 'anyelement',
   proargtypes => 'anyelement anyelement', prosrc => 'any_value_transfn' },
 
+{ oid => '6393', descr => 'inject delay during checkpoint',
+  proname => 'pg_test_inject_checkpoint_delay', provolatile => 'v', prorettype => 'void',
+  proargtypes => 'bool', prosrc => 'pg_test_inject_checkpoint_delay' },
+
+{ oid => '6394', descr => 'inject panic during checkpoint',
+  proname => 'pg_test_inject_checkpoint_panic', provolatile => 'v', prorettype => 'void',
+  proargtypes => 'bool', prosrc => 'pg_test_inject_checkpoint_panic' },
+
 ]
diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h
index a66722873f..3b845680ff 100644
--- a/src/include/postmaster/bgwriter.h
+++ b/src/include/postmaster/bgwriter.h
@@ -42,4 +42,7 @@ extern void CheckpointerShmemInit(void);
 
 extern bool FirstCallSinceLastCheckpoint(void);
 
+extern bool IsCheckpointDelayed(void);
+extern bool IsCheckpointPanicInjected(void);
+
 #endif							/* _BGWRITER_H */
diff --git a/src/test/recovery/t/037_add_duplicate_2pc_during_recovery.pl b/src/test/recovery/t/037_add_duplicate_2pc_during_recovery.pl
new file mode 100644
index 0000000000..9896c65bf9
--- /dev/null
+++ b/src/test/recovery/t/037_add_duplicate_2pc_during_recovery.pl
@@ -0,0 +1,68 @@
+
+# Copyright (c) 2021-2023, PostgreSQL Global Development Group
+
+# Tests add duplicate two-phase transaction during recovery
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More tests => 1;
+
+# Setup primary node
+my $node_primary = PostgreSQL::Test::Cluster->new("primary");
+$node_primary->init;
+$node_primary->append_conf(
+	'postgresql.conf', qq(
+	max_prepared_transactions = 10
+	log_checkpoints = true
+	checkpoint_timeout = 3000
+));
+$node_primary->start;
+
+$node_primary->psql('postgres', "create table t_test_2pc(id int, msg text)");
+$node_primary->psql('postgres', "select pg_test_inject_checkpoint_delay(true)");
+
+# start do checkpoint1
+my $host = $node_primary->host;
+my $port = $node_primary->port;
+`nohup psql -h $host -p $port -c 'checkpoint' >tmp_check/test.file 2>&1 &`;
+
+# prepare transaction during checkpoint1
+$node_primary->psql(
+	'postgres', "
+	BEGIN;
+	INSERT INTO t_test_2pc VALUES (1, 'test add duplicate 2pc');
+	PREPARE TRANSACTION 'xact_test_1';");
+
+# reset delay flag to finish checkpoint1
+$node_primary->psql('postgres', "select pg_test_inject_checkpoint_delay(false)");
+
+my $logdir = $node_primary->logfile;
+my $found = 0;
+my @result;
+while ($found == 0)
+{
+	@result = `grep -rn "checkpoint complete" $logdir`;
+	$found = @result;
+}
+print "checkpoint1 finished\n";
+
+# inject panic during checkpoint2
+$node_primary->psql('postgres', "select pg_test_inject_checkpoint_panic(true)");
+$node_primary->psql('postgres', "checkpoint");
+
+$found = 0;
+while ($found == 0)
+{
+	@result = `grep -rn "terminating any other active server processes" $logdir`;
+	$found = @result;
+}
+
+@result = `grep -rn "panic is injected during checkpoint" $logdir`;
+$found = @result;
+ok($found == 1, "inject panic success");
+
+# start will fail due to error in lock_twophase_recover()
+$node_primary->_update_pid(0);
+$node_primary->start;
\ No newline at end of file
-- 
2.19.1.6.gb485710b

