5656#include "pg_trace.h"
5757#include "pgstat.h"
5858#include "storage/fd.h"
59+ #include "storage/ipc.h"
5960#include "storage/procarray.h"
6061#include "storage/smgr.h"
6162#include "utils/builtins.h"
@@ -81,25 +82,25 @@ intmax_prepared_xacts = 0;
8182 *
8283 * The lifecycle of a global transaction is:
8384 *
84- * 1. After checking that the requested GID is not in use, set up an
85- *entry in the TwoPhaseState->prepXacts array with the correctXID andGID ,
86- *with locking_xid = my own XID and valid = false .
85+ * 1. After checking that the requested GID is not in use, set up an entry in
86+ * the TwoPhaseState->prepXacts array with the correctGID andvalid = false ,
87+ *and mark it as locked by my backend .
8788 *
8889 * 2. After successfully completing prepare, set valid = true and enter the
8990 * contained PGPROC into the global ProcArray.
9091 *
91- * 3. To begin COMMIT PREPARED or ROLLBACK PREPARED, check that the entry
92- *is valid andits locking_xid is no longer active, then store my current
93- *XID intolocking_xid . This prevents concurrent attempts to commit or
94- * rollback the same prepared xact.
92+ * 3. To begin COMMIT PREPARED or ROLLBACK PREPARED, check that the entry is
93+ * valid andnot locked, then mark the entry as locked by storing my current
94+ *backend ID intolocking_backend . This prevents concurrent attempts to
95+ *commit or rollback the same prepared xact.
9596 *
9697 * 4. On completion of COMMIT PREPARED or ROLLBACK PREPARED, remove the entry
9798 * from the ProcArray and the TwoPhaseState->prepXacts array and return it to
9899 * the freelist.
99100 *
100101 * Note that if the preparing transaction fails between steps 1 and 2, the
101- * entrywill remain in prepXacts until recycled. We can detect recyclable
102- *entries by checking for valid = false and locking_xid no longer active .
102+ * entrymust be removed so that the GID and the GlobalTransaction struct
103+ *can be reused. See AtAbort_Twophase() .
103104 *
104105 * typedef struct GlobalTransactionData *GlobalTransaction appears in
105106 * twophase.h
@@ -113,8 +114,8 @@ typedef struct GlobalTransactionData
113114TimestampTz prepared_at ;/* time of preparation */
114115XLogRecPtr prepare_lsn ;/* XLOG offset of prepare record */
115116Oid owner ;/* ID of user that executed the xact */
116- TransactionId locking_xid ; /*top-level XID of backend working on xact */
117- bool valid ;/* TRUE iffully prepared */
117+ BackendId locking_backend ; /* backendcurrently working on the xact */
118+ bool valid ;/* TRUE ifPGPROC entry is in proc array */
118119char gid [GIDSIZE ];/* The GID assigned to the prepared xact */
119120}GlobalTransactionData ;
120121
@@ -139,6 +140,12 @@ typedef struct TwoPhaseStateData
139140
140141static TwoPhaseStateData * TwoPhaseState ;
141142
143+ /*
144+ * Global transaction entry currently locked by us, if any.
145+ */
146+ static GlobalTransaction MyLockedGxact = NULL ;
147+
148+ static bool twophaseExitRegistered = false;
142149
143150static void RecordTransactionCommitPrepared (TransactionId xid ,
144151int nchildren ,
@@ -152,6 +159,7 @@ static void RecordTransactionAbortPrepared(TransactionId xid,
152159RelFileNode * rels );
153160static void ProcessRecords (char * bufptr ,TransactionId xid ,
154161const TwoPhaseCallback callbacks []);
162+ static void RemoveGXact (GlobalTransaction gxact );
155163
156164
157165/*
@@ -221,6 +229,74 @@ TwoPhaseShmemInit(void)
221229Assert (found );
222230}
223231
232+ /*
233+ * Exit hook to unlock the global transaction entry we're working on.
234+ */
235+ static void
236+ AtProcExit_Twophase (int code ,Datum arg )
237+ {
238+ /* same logic as abort */
239+ AtAbort_Twophase ();
240+ }
241+
242+ /*
243+ * Abort hook to unlock the global transaction entry we're working on.
244+ */
245+ void
246+ AtAbort_Twophase (void )
247+ {
248+ if (MyLockedGxact == NULL )
249+ return ;
250+
251+ /*
252+ * What to do with the locked global transaction entry? If we were in
253+ * the process of preparing the transaction, but haven't written the WAL
254+ * record and state file yet, the transaction must not be considered as
255+ * prepared. Likewise, if we are in the process of finishing an
256+ * already-prepared transaction, and fail after having already written
257+ * the 2nd phase commit or rollback record to the WAL, the transaction
258+ * should not be considered as prepared anymore. In those cases, just
259+ * remove the entry from shared memory.
260+ *
261+ * Otherwise, the entry must be left in place so that the transaction
262+ * can be finished later, so just unlock it.
263+ *
264+ * If we abort during prepare, after having written the WAL record, we
265+ * might not have transfered all locks and other state to the prepared
266+ * transaction yet. Likewise, if we abort during commit or rollback,
267+ * after having written the WAL record, we might not have released
268+ * all the resources held by the transaction yet. In those cases, the
269+ * in-memory state can be wrong, but it's too late to back out.
270+ */
271+ if (!MyLockedGxact -> valid )
272+ {
273+ RemoveGXact (MyLockedGxact );
274+ }
275+ else
276+ {
277+ LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
278+
279+ MyLockedGxact -> locking_backend = InvalidBackendId ;
280+
281+ LWLockRelease (TwoPhaseStateLock );
282+ }
283+ MyLockedGxact = NULL ;
284+ }
285+
286+ /*
287+ * This is called after we have finished transfering state to the prepared
288+ * PGXACT entry.
289+ */
290+ void
291+ PostPrepare_Twophase ()
292+ {
293+ LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
294+ MyLockedGxact -> locking_backend = InvalidBackendId ;
295+ LWLockRelease (TwoPhaseStateLock );
296+
297+ MyLockedGxact = NULL ;
298+ }
299+
224300
225301/*
226302 * MarkAsPreparing
@@ -250,29 +326,15 @@ MarkAsPreparing(TransactionId xid, const char *gid,
250326errmsg ("prepared transactions are disabled" ),
251327errhint ("Set max_prepared_transactions to a nonzero value." )));
252328
253- LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
254-
255- /*
256- * First, find and recycle any gxacts that failed during prepare. We do
257- * this partly to ensure we don't mistakenly say their GIDs are still
258- * reserved, and partly so we don't fail on out-of-slots unnecessarily.
259- */
260- for (i = 0 ;i < TwoPhaseState -> numPrepXacts ;i ++ )
329+ /* on first call, register the exit hook */
330+ if (!twophaseExitRegistered )
261331{
262- gxact = TwoPhaseState -> prepXacts [i ];
263- if (!gxact -> valid && !TransactionIdIsActive (gxact -> locking_xid ))
264- {
265- /* It's dead Jim ... remove from the active array */
266- TwoPhaseState -> numPrepXacts -- ;
267- TwoPhaseState -> prepXacts [i ]= TwoPhaseState -> prepXacts [TwoPhaseState -> numPrepXacts ];
268- /* and put it back in the freelist */
269- gxact -> proc .links .next = (SHM_QUEUE * )TwoPhaseState -> freeGXacts ;
270- TwoPhaseState -> freeGXacts = gxact ;
271- /* Back up index count too, so we don't miss scanning one */
272- i -- ;
273- }
332+ on_shmem_exit (AtProcExit_Twophase ,0 );
333+ twophaseExitRegistered = true;
274334}
275335
336+ LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
337+
276338/* Check for conflicting GID */
277339for (i = 0 ;i < TwoPhaseState -> numPrepXacts ;i ++ )
278340{
@@ -326,14 +388,20 @@ MarkAsPreparing(TransactionId xid, const char *gid,
326388gxact -> prepare_lsn .xlogid = 0 ;
327389gxact -> prepare_lsn .xrecoff = 0 ;
328390gxact -> owner = owner ;
329- gxact -> locking_xid = xid ;
391+ gxact -> locking_backend = MyBackendId ;
330392gxact -> valid = false;
331393strcpy (gxact -> gid ,gid );
332394
333395/* And insert it into the active array */
334396Assert (TwoPhaseState -> numPrepXacts < max_prepared_xacts );
335397TwoPhaseState -> prepXacts [TwoPhaseState -> numPrepXacts ++ ]= gxact ;
336398
399+ /*
400+ * Remember that we have this GlobalTransaction entry locked for us.
401+ * If we abort after this, we must release it.
402+ */
403+ MyLockedGxact = gxact ;
404+
337405LWLockRelease (TwoPhaseStateLock );
338406
339407return gxact ;
@@ -393,6 +461,13 @@ LockGXact(const char *gid, Oid user)
393461{
394462int i ;
395463
464+ /* on first call, register the exit hook */
465+ if (!twophaseExitRegistered )
466+ {
467+ on_shmem_exit (AtProcExit_Twophase ,0 );
468+ twophaseExitRegistered = true;
469+ }
470+
396471LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
397472
398473for (i = 0 ;i < TwoPhaseState -> numPrepXacts ;i ++ )
@@ -406,15 +481,11 @@ LockGXact(const char *gid, Oid user)
406481continue ;
407482
408483/* Found it, but has someone else got it locked? */
409- if (TransactionIdIsValid (gxact -> locking_xid ))
410- {
411- if (TransactionIdIsActive (gxact -> locking_xid ))
412- ereport (ERROR ,
413- (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
414- errmsg ("prepared transaction with identifier \"%s\" is busy" ,
415- gid )));
416- gxact -> locking_xid = InvalidTransactionId ;
417- }
484+ if (gxact -> locking_backend != InvalidBackendId )
485+ ereport (ERROR ,
486+ (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
487+ errmsg ("prepared transaction with identifier \"%s\" is busy" ,
488+ gid )));
418489
419490if (user != gxact -> owner && !superuser_arg (user ))
420491ereport (ERROR ,
@@ -435,7 +506,8 @@ LockGXact(const char *gid, Oid user)
435506errhint ("Connect to the database where the transaction was prepared to finish it." )));
436507
437508/* OK for me to lock it */
438- gxact -> locking_xid = GetTopTransactionId ();
509+ gxact -> locking_backend = MyBackendId ;
510+ MyLockedGxact = gxact ;
439511
440512LWLockRelease (TwoPhaseStateLock );
441513
@@ -1041,6 +1113,13 @@ EndPrepare(GlobalTransaction gxact)
10411113 */
10421114MyProc -> inCommit = false;
10431115
1116+ /*
1117+ * Remember that we have this GlobalTransaction entry locked for us. If
1118+ * we crash after this point, it's too late to abort, but we must unlock
1119+ * it so that the prepared transaction can be committed or rolled back.
1120+ */
1121+ MyLockedGxact = gxact ;
1122+
10441123END_CRIT_SECTION ();
10451124
10461125records .tail = records .head = NULL ;
@@ -1240,8 +1319,9 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
12401319
12411320/*
12421321 * In case we fail while running the callbacks, mark the gxact invalid so
1243- * no one else will try to commit/rollback, and so it can be recycled
1244- * properly later. It is still locked by our XID so it won't go away yet.
1322+ * no one else will try to commit/rollback, and so it will be recycled
1323+ * if we fail after this point. It is still locked by our backend so it
1324+ * won't go away yet.
12451325 *
12461326 * (We assume it's safe to do this without taking TwoPhaseStateLock.)
12471327 */
@@ -1291,6 +1371,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
12911371RemoveTwoPhaseFile (xid , true);
12921372
12931373RemoveGXact (gxact );
1374+ MyLockedGxact = NULL ;
12941375
12951376pfree (buf );
12961377}