5858#include "replication/walsender.h"
5959#include "replication/syncrep.h"
6060#include "storage/fd.h"
61+ #include "storage/ipc.h"
6162#include "storage/predicate.h"
6263#include "storage/proc.h"
6364#include "storage/procarray.h"
@@ -82,25 +83,25 @@ int max_prepared_xacts = 0;
8283 *
8384 * The lifecycle of a global transaction is:
8485 *
85- * 1. After checking that the requested GID is not in use, set up an
86- * entry in the TwoPhaseState->prepXacts array with the correct XID and GID ,
87- * with locking_xid = my own XID and valid = false .
86+ * 1. After checking that the requested GID is not in use, set up an entry in
87+ * the TwoPhaseState->prepXacts array with the correct GID and valid = false ,
88+ * and mark it as locked by my backend .
8889 *
8990 * 2. After successfully completing prepare, set valid = true and enter the
9091 * referenced PGPROC into the global ProcArray.
9192 *
92- * 3. To begin COMMIT PREPARED or ROLLBACK PREPARED, check that the entry
93- * is valid and its locking_xid is no longer active, then store my current
94- * XID into locking_xid . This prevents concurrent attempts to commit or
95- * rollback the same prepared xact.
93+ * 3. To begin COMMIT PREPARED or ROLLBACK PREPARED, check that the entry is
94+ * valid and not locked, then mark the entry as locked by storing my current
95+ * backend ID into locking_backend . This prevents concurrent attempts to
96+ * commit or rollback the same prepared xact.
9697 *
9798 * 4. On completion of COMMIT PREPARED or ROLLBACK PREPARED, remove the entry
9899 * from the ProcArray and the TwoPhaseState->prepXacts array and return it to
99100 * the freelist.
100101 *
101102 * Note that if the preparing transaction fails between steps 1 and 2, the
102- * entry will remain in prepXacts until recycled. We can detect recyclable
103- * entries by checking for valid = false and locking_xid no longer active .
103+ * entry must be removed so that the GID and the GlobalTransaction struct
104+ * can be reused. See AtAbort_Twophase() .
104105 *
105106 * typedef struct GlobalTransactionData *GlobalTransaction appears in
106107 * twophase.h
@@ -115,8 +116,8 @@ typedef struct GlobalTransactionData
115116 TimestampTz prepared_at ; /* time of preparation */
116117 XLogRecPtr prepare_lsn ; /* XLOG offset of prepare record */
117118 Oid owner ; /* ID of user that executed the xact */
118- TransactionId locking_xid ; /* top-level XID of backend working on xact */
119- bool valid ; /* TRUE if fully prepared */
119+ BackendId locking_backend ; /* backend currently working on the xact */
120+ bool valid ; /* TRUE if PGPROC entry is in proc array */
120121 char gid [GIDSIZE ]; /* The GID assigned to the prepared xact */
121122} GlobalTransactionData ;
122123
@@ -141,6 +142,12 @@ typedef struct TwoPhaseStateData
141142
142143static TwoPhaseStateData * TwoPhaseState ;
143144
145+ /*
146+ * Global transaction entry currently locked by us, if any.
147+ */
148+ static GlobalTransaction MyLockedGxact = NULL ;
149+
150+ static bool twophaseExitRegistered = false;
144151
145152static void RecordTransactionCommitPrepared (TransactionId xid ,
146153 int nchildren ,
@@ -157,6 +164,7 @@ static void RecordTransactionAbortPrepared(TransactionId xid,
157164 RelFileNode * rels );
158165static void ProcessRecords (char * bufptr , TransactionId xid ,
159166 const TwoPhaseCallback callbacks []);
167+ static void RemoveGXact (GlobalTransaction gxact );
160168
161169
162170/*
@@ -230,6 +238,74 @@ TwoPhaseShmemInit(void)
230238 Assert (found );
231239}
232240
241+ /*
242+ * Exit hook to unlock the global transaction entry we're working on.
243+ */
244+ static void
245+ AtProcExit_Twophase (int code , Datum arg )
246+ {
247+ /* same logic as abort */
248+ AtAbort_Twophase ();
249+ }
250+
251+ /*
252+ * Abort hook to unlock the global transaction entry we're working on.
253+ */
254+ void
255+ AtAbort_Twophase (void )
256+ {
257+ if (MyLockedGxact == NULL )
258+ return ;
259+
260+ /*
261+ * What to do with the locked global transaction entry? If we were in
262+ * the process of preparing the transaction, but haven't written the WAL
263+ * record and state file yet, the transaction must not be considered as
264+ * prepared. Likewise, if we are in the process of finishing an
265+ * already-prepared transaction, and fail after having already written
266+ * the 2nd phase commit or rollback record to the WAL, the transaction
267+ * should not be considered as prepared anymore. In those cases, just
268+ * remove the entry from shared memory.
269+ *
270+ * Otherwise, the entry must be left in place so that the transaction
271+ * can be finished later, so just unlock it.
272+ *
273+ * If we abort during prepare, after having written the WAL record, we
274+ * might not have transfered all locks and other state to the prepared
275+ * transaction yet. Likewise, if we abort during commit or rollback,
276+ * after having written the WAL record, we might not have released
277+ * all the resources held by the transaction yet. In those cases, the
278+ * in-memory state can be wrong, but it's too late to back out.
279+ */
280+ if (!MyLockedGxact -> valid )
281+ {
282+ RemoveGXact (MyLockedGxact );
283+ }
284+ else
285+ {
286+ LWLockAcquire (TwoPhaseStateLock , LW_EXCLUSIVE );
287+
288+ MyLockedGxact -> locking_backend = InvalidBackendId ;
289+
290+ LWLockRelease (TwoPhaseStateLock );
291+ }
292+ MyLockedGxact = NULL ;
293+ }
294+
295+ /*
296+ * This is called after we have finished transfering state to the prepared
297+ * PGXACT entry.
298+ */
299+ void
300+ PostPrepare_Twophase ()
301+ {
302+ LWLockAcquire (TwoPhaseStateLock , LW_EXCLUSIVE );
303+ MyLockedGxact -> locking_backend = InvalidBackendId ;
304+ LWLockRelease (TwoPhaseStateLock );
305+
306+ MyLockedGxact = NULL ;
307+ }
308+
233309
234310/*
235311 * MarkAsPreparing
@@ -261,29 +337,15 @@ MarkAsPreparing(TransactionId xid, const char *gid,
261337 errmsg ("prepared transactions are disabled" ),
262338 errhint ("Set max_prepared_transactions to a nonzero value." )));
263339
264- LWLockAcquire (TwoPhaseStateLock , LW_EXCLUSIVE );
265-
266- /*
267- * First, find and recycle any gxacts that failed during prepare. We do
268- * this partly to ensure we don't mistakenly say their GIDs are still
269- * reserved, and partly so we don't fail on out-of-slots unnecessarily.
270- */
271- for (i = 0 ; i < TwoPhaseState -> numPrepXacts ; i ++ )
340+ /* on first call, register the exit hook */
341+ if (!twophaseExitRegistered )
272342 {
273- gxact = TwoPhaseState -> prepXacts [i ];
274- if (!gxact -> valid && !TransactionIdIsActive (gxact -> locking_xid ))
275- {
276- /* It's dead Jim ... remove from the active array */
277- TwoPhaseState -> numPrepXacts -- ;
278- TwoPhaseState -> prepXacts [i ] = TwoPhaseState -> prepXacts [TwoPhaseState -> numPrepXacts ];
279- /* and put it back in the freelist */
280- gxact -> next = TwoPhaseState -> freeGXacts ;
281- TwoPhaseState -> freeGXacts = gxact ;
282- /* Back up index count too, so we don't miss scanning one */
283- i -- ;
284- }
343+ before_shmem_exit (AtProcExit_Twophase , 0 );
344+ twophaseExitRegistered = true;
285345 }
286346
347+ LWLockAcquire (TwoPhaseStateLock , LW_EXCLUSIVE );
348+
287349 /* Check for conflicting GID */
288350 for (i = 0 ; i < TwoPhaseState -> numPrepXacts ; i ++ )
289351 {
@@ -340,14 +402,20 @@ MarkAsPreparing(TransactionId xid, const char *gid,
340402 /* initialize LSN to 0 (start of WAL) */
341403 gxact -> prepare_lsn = 0 ;
342404 gxact -> owner = owner ;
343- gxact -> locking_xid = xid ;
405+ gxact -> locking_backend = MyBackendId ;
344406 gxact -> valid = false;
345407 strcpy (gxact -> gid , gid );
346408
347409 /* And insert it into the active array */
348410 Assert (TwoPhaseState -> numPrepXacts < max_prepared_xacts );
349411 TwoPhaseState -> prepXacts [TwoPhaseState -> numPrepXacts ++ ] = gxact ;
350412
413+ /*
414+ * Remember that we have this GlobalTransaction entry locked for us.
415+ * If we abort after this, we must release it.
416+ */
417+ MyLockedGxact = gxact ;
418+
351419 LWLockRelease (TwoPhaseStateLock );
352420
353421 return gxact ;
@@ -410,6 +478,13 @@ LockGXact(const char *gid, Oid user)
410478{
411479 int i ;
412480
481+ /* on first call, register the exit hook */
482+ if (!twophaseExitRegistered )
483+ {
484+ before_shmem_exit (AtProcExit_Twophase , 0 );
485+ twophaseExitRegistered = true;
486+ }
487+
413488 LWLockAcquire (TwoPhaseStateLock , LW_EXCLUSIVE );
414489
415490 for (i = 0 ; i < TwoPhaseState -> numPrepXacts ; i ++ )
@@ -424,15 +499,11 @@ LockGXact(const char *gid, Oid user)
424499 continue ;
425500
426501 /* Found it, but has someone else got it locked? */
427- if (TransactionIdIsValid (gxact -> locking_xid ))
428- {
429- if (TransactionIdIsActive (gxact -> locking_xid ))
430- ereport (ERROR ,
431- (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
432- errmsg ("prepared transaction with identifier \"%s\" is busy" ,
433- gid )));
434- gxact -> locking_xid = InvalidTransactionId ;
435- }
502+ if (gxact -> locking_backend != InvalidBackendId )
503+ ereport (ERROR ,
504+ (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
505+ errmsg ("prepared transaction with identifier \"%s\" is busy" ,
506+ gid )));
436507
437508 if (user != gxact -> owner && !superuser_arg (user ))
438509 ereport (ERROR ,
@@ -453,7 +524,8 @@ LockGXact(const char *gid, Oid user)
453524 errhint ("Connect to the database where the transaction was prepared to finish it." )));
454525
455526 /* OK for me to lock it */
456- gxact -> locking_xid = GetTopTransactionId ();
527+ gxact -> locking_backend = MyBackendId ;
528+ MyLockedGxact = gxact ;
457529
458530 LWLockRelease (TwoPhaseStateLock );
459531
@@ -1089,6 +1161,13 @@ EndPrepare(GlobalTransaction gxact)
10891161 */
10901162 MyPgXact -> delayChkpt = false;
10911163
1164+ /*
1165+ * Remember that we have this GlobalTransaction entry locked for us. If
1166+ * we crash after this point, it's too late to abort, but we must unlock
1167+ * it so that the prepared transaction can be committed or rolled back.
1168+ */
1169+ MyLockedGxact = gxact ;
1170+
10921171 END_CRIT_SECTION ();
10931172
10941173 /*
@@ -1335,8 +1414,9 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
13351414
13361415 /*
13371416 * In case we fail while running the callbacks, mark the gxact invalid so
1338- * no one else will try to commit/rollback, and so it can be recycled
1339- * properly later. It is still locked by our XID so it won't go away yet.
1417+ * no one else will try to commit/rollback, and so it will be recycled
1418+ * if we fail after this point. It is still locked by our backend so it
1419+ * won't go away yet.
13401420 *
13411421 * (We assume it's safe to do this without taking TwoPhaseStateLock.)
13421422 */
@@ -1396,6 +1476,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
13961476 RemoveTwoPhaseFile (xid , true);
13971477
13981478 RemoveGXact (gxact );
1479+ MyLockedGxact = NULL ;
13991480
14001481 pfree (buf );
14011482}
0 commit comments