aboutsummaryrefslogtreecommitdiff
path: root/src/backend/access/transam/multixact.c
diff options
context:
space:
mode:
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>2009-11-23 09:59:21 +0000
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>2009-11-23 09:59:21 +0000
commitc63dbfff8379622de9def83827a6af610684467a (patch)
tree5a4d073d88fab095324f7dbc421667281f1d5787 /src/backend/access/transam/multixact.c
parentfd20a349c4c3e1b46b0fe81b369e0f877259d9ee (diff)
downloadpostgresql-c63dbfff8379622de9def83827a6af610684467a.tar.gz
postgresql-c63dbfff8379622de9def83827a6af610684467a.zip
Fix an old bug in multixact and two-phase commit. Prepared transactions can
be part of multixacts, so allocate a slot for each prepared transaction in the "oldest member" array in multixact.c. On PREPARE TRANSACTION, transfer the oldest member value from the current backends slot to the prepared xact slot. Also save and recover the value from the 2pc state file. The symptom of the bug was that after a transaction prepared, a shared lock still held by the prepared transaction was sometimes ignored by other transactions. Fix back to 8.1, where both 2PC and multixact were introduced.
Diffstat (limited to 'src/backend/access/transam/multixact.c')
-rw-r--r--src/backend/access/transam/multixact.c140
1 files changed, 132 insertions, 8 deletions
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 44861f323be..65f266275dd 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -42,7 +42,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/access/transam/multixact.c,v 1.11.2.3 2006/11/17 18:00:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/multixact.c,v 1.11.2.4 2009/11/23 09:59:21 heikki Exp $
*
*-------------------------------------------------------------------------
*/
@@ -50,6 +50,8 @@
#include "access/multixact.h"
#include "access/slru.h"
+#include "access/twophase.h"
+#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "miscadmin.h"
#include "utils/memutils.h"
@@ -116,8 +118,11 @@ typedef struct MultiXactStateData
/*
* Per-backend data starts here. We have two arrays stored in the area
* immediately following the MultiXactStateData struct. Each is indexed by
- * BackendId. (Note: valid BackendIds run from 1 to MaxBackends; element
- * zero of each array is never used.)
+ * BackendId.
+ *
+ * In both arrays, there's a slot for all normal backends (1..MaxBackends)
+ * followed by a slot for max_prepared_xacts prepared transactions. Valid
+ * BackendIds start from 1; element zero of each array is never used.
*
* OldestMemberMXactId[k] is the oldest MultiXactId each backend's current
* transaction(s) could possibly be a member of, or InvalidMultiXactId
@@ -150,6 +155,12 @@ typedef struct MultiXactStateData
MultiXactId perBackendXactIds[1]; /* VARIABLE LENGTH ARRAY */
} MultiXactStateData;
+/*
+ * Last element of OldestMemberMXactID and OldestVisibleMXactId arrays.
+ * Valid elements are (1..MaxOldestSlot); element 0 is never used.
+ */
+#define MaxOldestSlot (MaxBackends + max_prepared_xacts)
+
/* Pointers to the state data in shared memory */
static MultiXactStateData *MultiXactState;
static MultiXactId *OldestMemberMXactId;
@@ -537,7 +548,7 @@ MultiXactIdSetOldestVisible(void)
if (oldestMXact < FirstMultiXactId)
oldestMXact = FirstMultiXactId;
- for (i = 1; i <= MaxBackends; i++)
+ for (i = 1; i <= MaxOldestSlot; i++)
{
MultiXactId thisoldest = OldestMemberMXactId[i];
@@ -1274,6 +1285,119 @@ AtEOXact_MultiXact(void)
}
/*
+ * AtPrepare_MultiXact
+ * Save multixact state at 2PC tranasction prepare
+ *
+ * In this phase, we only store our OldestMemberMXactId value in the two-phase
+ * state file.
+ */
+void
+AtPrepare_MultiXact(void)
+{
+ MultiXactId myOldestMember = OldestMemberMXactId[MyBackendId];
+
+ if (MultiXactIdIsValid(myOldestMember))
+ RegisterTwoPhaseRecord(TWOPHASE_RM_MULTIXACT_ID, 0,
+ &myOldestMember, sizeof(MultiXactId));
+}
+
+/*
+ * PostPrepare_MultiXact
+ * Clean up after successful PREPARE TRANSACTION
+ */
+void
+PostPrepare_MultiXact(TransactionId xid)
+{
+ MultiXactId myOldestMember;
+
+ /*
+ * Transfer our OldestMemberMXactId value to the slot reserved for the
+ * prepared transaction.
+ */
+ myOldestMember = OldestMemberMXactId[MyBackendId];
+ if (MultiXactIdIsValid(myOldestMember))
+ {
+ BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
+
+ /*
+ * Even though storing MultiXactId is atomic, acquire lock to make sure
+ * others see both changes, not just the reset of the slot of the
+ * current backend. Using a volatile pointer might suffice, but this
+ * isn't a hot spot.
+ */
+ LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
+
+ OldestMemberMXactId[dummyBackendId] = myOldestMember;
+ OldestMemberMXactId[MyBackendId] = InvalidMultiXactId;
+
+ LWLockRelease(MultiXactGenLock);
+ }
+
+ /*
+ * We don't need to transfer OldestVisibleMXactId value, because the
+ * transaction is not going to be looking at any more multixacts once
+ * it's prepared.
+ *
+ * We assume that storing a MultiXactId is atomic and so we need not take
+ * MultiXactGenLock to do this.
+ */
+ OldestVisibleMXactId[MyBackendId] = InvalidMultiXactId;
+
+ /*
+ * Discard the local MultiXactId cache like in AtEOX_MultiXact
+ */
+ MXactContext = NULL;
+ MXactCache = NULL;
+}
+
+/*
+ * multixact_twophase_recover
+ * Recover the state of a prepared transaction at startup
+ */
+void
+multixact_twophase_recover(TransactionId xid, uint16 info,
+ void *recdata, uint32 len)
+{
+ BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
+ MultiXactId oldestMember;
+
+ /*
+ * Get the oldest member XID from the state file record, and set it in
+ * the OldestMemberMXactId slot reserved for this prepared transaction.
+ */
+ Assert(len == sizeof(MultiXactId));
+ oldestMember = *((MultiXactId *)recdata);
+
+ OldestMemberMXactId[dummyBackendId] = oldestMember;
+}
+
+/*
+ * multixact_twophase_postcommit
+ * Similar to AtEOX_MultiXact but for COMMIT PREPARED
+ */
+void
+multixact_twophase_postcommit(TransactionId xid, uint16 info,
+ void *recdata, uint32 len)
+{
+ BackendId dummyBackendId = TwoPhaseGetDummyBackendId(xid);
+
+ Assert(len == sizeof(MultiXactId));
+
+ OldestMemberMXactId[dummyBackendId] = InvalidMultiXactId;
+}
+
+/*
+ * multixact_twophase_postabort
+ * This is actually just the same as the COMMIT case.
+ */
+void
+multixact_twophase_postabort(TransactionId xid, uint16 info,
+ void *recdata, uint32 len)
+{
+ multixact_twophase_postcommit(xid, info, recdata, len);
+}
+
+/*
* Initialization of shared memory for MultiXact. We use two SLRU areas,
* thus double memory. Also, reserve space for the shared MultiXactState
* struct and the per-backend MultiXactId arrays (two of those, too).
@@ -1285,7 +1409,7 @@ MultiXactShmemSize(void)
#define SHARED_MULTIXACT_STATE_SIZE \
add_size(sizeof(MultiXactStateData), \
- mul_size(sizeof(MultiXactId) * 2, MaxBackends))
+ mul_size(sizeof(MultiXactId) * 2, MaxOldestSlot))
size = SHARED_MULTIXACT_STATE_SIZE;
size = add_size(size, SimpleLruShmemSize());
@@ -1325,10 +1449,10 @@ MultiXactShmemInit(void)
/*
* Set up array pointers. Note that perBackendXactIds[0] is wasted space
- * since we only use indexes 1..MaxBackends in each array.
+ * since we only use indexes 1..MaxOldestSlot in each array.
*/
OldestMemberMXactId = MultiXactState->perBackendXactIds;
- OldestVisibleMXactId = OldestMemberMXactId + MaxBackends;
+ OldestVisibleMXactId = OldestMemberMXactId + MaxOldestSlot;
}
/*
@@ -1692,7 +1816,7 @@ TruncateMultiXact(void)
nextMXact = FirstMultiXactId;
oldestMXact = nextMXact;
- for (i = 1; i <= MaxBackends; i++)
+ for (i = 1; i <= MaxOldestSlot; i++)
{
MultiXactId thisoldest;