56
56
#include "pg_trace.h"
57
57
#include "pgstat.h"
58
58
#include "storage/fd.h"
59
+ #include "storage/ipc.h"
59
60
#include "storage/procarray.h"
60
61
#include "storage/sinvaladt.h"
61
62
#include "storage/smgr.h"
@@ -82,25 +83,25 @@ intmax_prepared_xacts = 0;
82
83
*
83
84
* The lifecycle of a global transaction is:
84
85
*
85
- * 1. After checking that the requested GID is not in use, set up an
86
- *entry in the TwoPhaseState->prepXacts array with the correctXID andGID ,
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 correctGID andvalid = false ,
88
+ *and mark it as locked by my backend .
88
89
*
89
90
* 2. After successfully completing prepare, set valid = true and enter the
90
91
* contained PGPROC into the global ProcArray.
91
92
*
92
- * 3. To begin COMMIT PREPARED or ROLLBACK PREPARED, check that the entry
93
- *is valid andits locking_xid is no longer active, then store my current
94
- *XID intolocking_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 andnot locked, then mark the entry as locked by storing my current
95
+ *backend ID intolocking_backend . This prevents concurrent attempts to
96
+ *commit or rollback the same prepared xact.
96
97
*
97
98
* 4. On completion of COMMIT PREPARED or ROLLBACK PREPARED, remove the entry
98
99
* from the ProcArray and the TwoPhaseState->prepXacts array and return it to
99
100
* the freelist.
100
101
*
101
102
* Note that if the preparing transaction fails between steps 1 and 2, the
102
- * entrywill remain in prepXacts until recycled. We can detect recyclable
103
- *entries by checking for valid = false and locking_xid no longer active .
103
+ * entrymust be removed so that the GID and the GlobalTransaction struct
104
+ *can be reused. See AtAbort_Twophase() .
104
105
*
105
106
* typedef struct GlobalTransactionData *GlobalTransaction appears in
106
107
* twophase.h
@@ -114,8 +115,8 @@ typedef struct GlobalTransactionData
114
115
TimestampTz prepared_at ;/* time of preparation */
115
116
XLogRecPtr prepare_lsn ;/* XLOG offset of prepare record */
116
117
Oid owner ;/* ID of user that executed the xact */
117
- TransactionId locking_xid ; /*top-level XID of backend working on xact */
118
- bool valid ;/* TRUE iffully prepared */
118
+ BackendId locking_backend ; /* backendcurrently working on the xact */
119
+ bool valid ;/* TRUE ifPGPROC entry is in proc array */
119
120
char gid [GIDSIZE ];/* The GID assigned to the prepared xact */
120
121
}GlobalTransactionData ;
121
122
@@ -140,6 +141,12 @@ typedef struct TwoPhaseStateData
140
141
141
142
static TwoPhaseStateData * TwoPhaseState ;
142
143
144
+ /*
145
+ * Global transaction entry currently locked by us, if any.
146
+ */
147
+ static GlobalTransaction MyLockedGxact = NULL ;
148
+
149
+ static bool twophaseExitRegistered = false;
143
150
144
151
static void RecordTransactionCommitPrepared (TransactionId xid ,
145
152
int nchildren ,
@@ -156,6 +163,7 @@ static void RecordTransactionAbortPrepared(TransactionId xid,
156
163
RelFileNode * rels );
157
164
static void ProcessRecords (char * bufptr ,TransactionId xid ,
158
165
const TwoPhaseCallback callbacks []);
166
+ static void RemoveGXact (GlobalTransaction gxact );
159
167
160
168
161
169
/*
@@ -225,6 +233,74 @@ TwoPhaseShmemInit(void)
225
233
Assert (found );
226
234
}
227
235
236
+ /*
237
+ * Exit hook to unlock the global transaction entry we're working on.
238
+ */
239
+ static void
240
+ AtProcExit_Twophase (int code ,Datum arg )
241
+ {
242
+ /* same logic as abort */
243
+ AtAbort_Twophase ();
244
+ }
245
+
246
+ /*
247
+ * Abort hook to unlock the global transaction entry we're working on.
248
+ */
249
+ void
250
+ AtAbort_Twophase (void )
251
+ {
252
+ if (MyLockedGxact == NULL )
253
+ return ;
254
+
255
+ /*
256
+ * What to do with the locked global transaction entry? If we were in
257
+ * the process of preparing the transaction, but haven't written the WAL
258
+ * record and state file yet, the transaction must not be considered as
259
+ * prepared. Likewise, if we are in the process of finishing an
260
+ * already-prepared transaction, and fail after having already written
261
+ * the 2nd phase commit or rollback record to the WAL, the transaction
262
+ * should not be considered as prepared anymore. In those cases, just
263
+ * remove the entry from shared memory.
264
+ *
265
+ * Otherwise, the entry must be left in place so that the transaction
266
+ * can be finished later, so just unlock it.
267
+ *
268
+ * If we abort during prepare, after having written the WAL record, we
269
+ * might not have transfered all locks and other state to the prepared
270
+ * transaction yet. Likewise, if we abort during commit or rollback,
271
+ * after having written the WAL record, we might not have released
272
+ * all the resources held by the transaction yet. In those cases, the
273
+ * in-memory state can be wrong, but it's too late to back out.
274
+ */
275
+ if (!MyLockedGxact -> valid )
276
+ {
277
+ RemoveGXact (MyLockedGxact );
278
+ }
279
+ else
280
+ {
281
+ LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
282
+
283
+ MyLockedGxact -> locking_backend = InvalidBackendId ;
284
+
285
+ LWLockRelease (TwoPhaseStateLock );
286
+ }
287
+ MyLockedGxact = NULL ;
288
+ }
289
+
290
+ /*
291
+ * This is called after we have finished transfering state to the prepared
292
+ * PGXACT entry.
293
+ */
294
+ void
295
+ PostPrepare_Twophase ()
296
+ {
297
+ LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
298
+ MyLockedGxact -> locking_backend = InvalidBackendId ;
299
+ LWLockRelease (TwoPhaseStateLock );
300
+
301
+ MyLockedGxact = NULL ;
302
+ }
303
+
228
304
229
305
/*
230
306
* MarkAsPreparing
@@ -254,29 +330,15 @@ MarkAsPreparing(TransactionId xid, const char *gid,
254
330
errmsg ("prepared transactions are disabled" ),
255
331
errhint ("Set max_prepared_transactions to a nonzero value." )));
256
332
257
- LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
258
-
259
- /*
260
- * First, find and recycle any gxacts that failed during prepare. We do
261
- * this partly to ensure we don't mistakenly say their GIDs are still
262
- * reserved, and partly so we don't fail on out-of-slots unnecessarily.
263
- */
264
- for (i = 0 ;i < TwoPhaseState -> numPrepXacts ;i ++ )
333
+ /* on first call, register the exit hook */
334
+ if (!twophaseExitRegistered )
265
335
{
266
- gxact = TwoPhaseState -> prepXacts [i ];
267
- if (!gxact -> valid && !TransactionIdIsActive (gxact -> locking_xid ))
268
- {
269
- /* It's dead Jim ... remove from the active array */
270
- TwoPhaseState -> numPrepXacts -- ;
271
- TwoPhaseState -> prepXacts [i ]= TwoPhaseState -> prepXacts [TwoPhaseState -> numPrepXacts ];
272
- /* and put it back in the freelist */
273
- gxact -> proc .links .next = (SHM_QUEUE * )TwoPhaseState -> freeGXacts ;
274
- TwoPhaseState -> freeGXacts = gxact ;
275
- /* Back up index count too, so we don't miss scanning one */
276
- i -- ;
277
- }
336
+ on_shmem_exit (AtProcExit_Twophase ,0 );
337
+ twophaseExitRegistered = true;
278
338
}
279
339
340
+ LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
341
+
280
342
/* Check for conflicting GID */
281
343
for (i = 0 ;i < TwoPhaseState -> numPrepXacts ;i ++ )
282
344
{
@@ -330,14 +392,20 @@ MarkAsPreparing(TransactionId xid, const char *gid,
330
392
gxact -> prepare_lsn .xlogid = 0 ;
331
393
gxact -> prepare_lsn .xrecoff = 0 ;
332
394
gxact -> owner = owner ;
333
- gxact -> locking_xid = xid ;
395
+ gxact -> locking_backend = MyBackendId ;
334
396
gxact -> valid = false;
335
397
strcpy (gxact -> gid ,gid );
336
398
337
399
/* And insert it into the active array */
338
400
Assert (TwoPhaseState -> numPrepXacts < max_prepared_xacts );
339
401
TwoPhaseState -> prepXacts [TwoPhaseState -> numPrepXacts ++ ]= gxact ;
340
402
403
+ /*
404
+ * Remember that we have this GlobalTransaction entry locked for us.
405
+ * If we abort after this, we must release it.
406
+ */
407
+ MyLockedGxact = gxact ;
408
+
341
409
LWLockRelease (TwoPhaseStateLock );
342
410
343
411
return gxact ;
@@ -397,6 +465,13 @@ LockGXact(const char *gid, Oid user)
397
465
{
398
466
int i ;
399
467
468
+ /* on first call, register the exit hook */
469
+ if (!twophaseExitRegistered )
470
+ {
471
+ on_shmem_exit (AtProcExit_Twophase ,0 );
472
+ twophaseExitRegistered = true;
473
+ }
474
+
400
475
LWLockAcquire (TwoPhaseStateLock ,LW_EXCLUSIVE );
401
476
402
477
for (i = 0 ;i < TwoPhaseState -> numPrepXacts ;i ++ )
@@ -410,15 +485,11 @@ LockGXact(const char *gid, Oid user)
410
485
continue ;
411
486
412
487
/* Found it, but has someone else got it locked? */
413
- if (TransactionIdIsValid (gxact -> locking_xid ))
414
- {
415
- if (TransactionIdIsActive (gxact -> locking_xid ))
416
- ereport (ERROR ,
417
- (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
418
- errmsg ("prepared transaction with identifier \"%s\" is busy" ,
419
- gid )));
420
- gxact -> locking_xid = InvalidTransactionId ;
421
- }
488
+ if (gxact -> locking_backend != InvalidBackendId )
489
+ ereport (ERROR ,
490
+ (errcode (ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE ),
491
+ errmsg ("prepared transaction with identifier \"%s\" is busy" ,
492
+ gid )));
422
493
423
494
if (user != gxact -> owner && !superuser_arg (user ))
424
495
ereport (ERROR ,
@@ -439,7 +510,8 @@ LockGXact(const char *gid, Oid user)
439
510
errhint ("Connect to the database where the transaction was prepared to finish it." )));
440
511
441
512
/* OK for me to lock it */
442
- gxact -> locking_xid = GetTopTransactionId ();
513
+ gxact -> locking_backend = MyBackendId ;
514
+ MyLockedGxact = gxact ;
443
515
444
516
LWLockRelease (TwoPhaseStateLock );
445
517
@@ -1060,6 +1132,13 @@ EndPrepare(GlobalTransaction gxact)
1060
1132
*/
1061
1133
MyProc -> inCommit = false;
1062
1134
1135
+ /*
1136
+ * Remember that we have this GlobalTransaction entry locked for us. If
1137
+ * we crash after this point, it's too late to abort, but we must unlock
1138
+ * it so that the prepared transaction can be committed or rolled back.
1139
+ */
1140
+ MyLockedGxact = gxact ;
1141
+
1063
1142
END_CRIT_SECTION ();
1064
1143
1065
1144
records .tail = records .head = NULL ;
@@ -1294,8 +1373,9 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
1294
1373
1295
1374
/*
1296
1375
* In case we fail while running the callbacks, mark the gxact invalid so
1297
- * no one else will try to commit/rollback, and so it can be recycled
1298
- * properly later. It is still locked by our XID so it won't go away yet.
1376
+ * no one else will try to commit/rollback, and so it will be recycled
1377
+ * if we fail after this point. It is still locked by our backend so it
1378
+ * won't go away yet.
1299
1379
*
1300
1380
* (We assume it's safe to do this without taking TwoPhaseStateLock.)
1301
1381
*/
@@ -1357,6 +1437,7 @@ FinishPreparedTransaction(const char *gid, bool isCommit)
1357
1437
RemoveTwoPhaseFile (xid , true);
1358
1438
1359
1439
RemoveGXact (gxact );
1440
+ MyLockedGxact = NULL ;
1360
1441
1361
1442
pfree (buf );
1362
1443
}