]> granicus.if.org Git - postgresql/commitdiff
Fix an old bug in multixact and two-phase commit. Prepared transactions can
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Nov 2009 09:59:00 +0000 (09:59 +0000)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Nov 2009 09:59:00 +0000 (09:59 +0000)
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.

src/backend/access/transam/multixact.c
src/backend/access/transam/twophase.c
src/backend/access/transam/twophase_rmgr.c
src/backend/access/transam/xact.c
src/include/access/multixact.h
src/include/access/twophase.h
src/include/access/twophase_rmgr.h

index 8ccd1cf5f900b0ae72e6dd568d6ac693e4bcff1d..617d159952394ba3a5be29cd2ff70364ccdd26db 100644 (file)
@@ -42,7 +42,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/access/transam/multixact.c,v 1.27 2008/01/01 19:45:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/multixact.c,v 1.27.2.1 2009/11/23 09:59:00 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,8 @@
 #include "access/multixact.h"
 #include "access/slru.h"
 #include "access/transam.h"
+#include "access/twophase.h"
+#include "access/twophase_rmgr.h"
 #include "access/xact.h"
 #include "miscadmin.h"
 #include "storage/backendid.h"
@@ -117,8 +119,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
@@ -151,6 +156,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;
@@ -538,7 +549,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)
        MXactCache = NULL;
 }
 
+/*
+ * 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
@@ -1286,7 +1410,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(NUM_MXACTOFFSET_BUFFERS, 0));
@@ -1328,10 +1452,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;
 }
 
 /*
@@ -1695,7 +1819,7 @@ TruncateMultiXact(void)
                nextMXact = FirstMultiXactId;
 
        oldestMXact = nextMXact;
-       for (i = 1; i <= MaxBackends; i++)
+       for (i = 1; i <= MaxOldestSlot; i++)
        {
                MultiXactId thisoldest;
 
index c38ce14289f378789bd825b0ba0e2b4e810d916b..725b99bf6e1a05f05f91ef972cf1dfeaa38c737a 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *             $PostgreSQL: pgsql/src/backend/access/transam/twophase.c,v 1.39.2.2 2008/05/19 18:16:46 heikki Exp $
+ *             $PostgreSQL: pgsql/src/backend/access/transam/twophase.c,v 1.39.2.3 2009/11/23 09:59:00 heikki Exp $
  *
  * NOTES
  *             Each global transaction is associated with a global transaction
@@ -106,6 +106,7 @@ int                 max_prepared_xacts = 5;
 typedef struct GlobalTransactionData
 {
        PGPROC          proc;                   /* dummy proc */
+       BackendId       dummyBackendId; /* similar to backend id for backends */
        TimestampTz prepared_at;        /* time of preparation */
        XLogRecPtr      prepare_lsn;    /* XLOG offset of prepare record */
        Oid                     owner;                  /* ID of user that executed the xact */
@@ -197,6 +198,20 @@ TwoPhaseShmemInit(void)
                {
                        gxacts[i].proc.links.next = TwoPhaseState->freeGXacts;
                        TwoPhaseState->freeGXacts = MAKE_OFFSET(&gxacts[i]);
+
+                       /*
+                        * Assign a unique ID for each dummy proc, so that the range of
+                        * dummy backend IDs immediately follows the range of normal
+                        * backend IDs. We don't dare to assign a real backend ID to
+                        * dummy procs, because prepared transactions don't take part in
+                        * cache invalidation like a real backend ID would imply, but
+                        * having a unique ID for them is nevertheless handy. This
+                        * arrangement allows you to allocate an array of size
+                        * (MaxBackends + max_prepared_xacts + 1), and have a slot for
+                        * every backend and prepared transaction. Currently multixact.c
+                        * uses that technique.
+                        */
+                       gxacts[i].dummyBackendId = MaxBackends + 1 + i;
                }
        }
        else
@@ -637,6 +652,22 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
        SRF_RETURN_DONE(funcctx);
 }
 
+/*
+ * TwoPhaseGetDummyProc
+ *             Get the dummy backend ID for prepared transaction specified by XID
+ *
+ * Dummy backend IDs are similar to real backend IDs of real backends.
+ * They start at MaxBackends + 1, and are unique across all currently active
+ * real backends and prepared transactions.
+ */
+BackendId
+TwoPhaseGetDummyBackendId(TransactionId xid)
+{
+       PGPROC *proc = TwoPhaseGetDummyProc(xid);
+
+       return ((GlobalTransaction) proc)->dummyBackendId;
+}
+
 /*
  * TwoPhaseGetDummyProc
  *             Get the PGPROC that represents a prepared transaction specified by XID
index 435e06fee4b72c626ca5703c65771bbae8045303..a7dfaf39c1d290c4a6439a6e29d02595c6a4a3bc 100644 (file)
@@ -8,12 +8,13 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/access/transam/twophase_rmgr.c,v 1.7 2008/01/01 19:45:48 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/access/transam/twophase_rmgr.c,v 1.7.2.1 2009/11/23 09:59:00 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "access/multixact.h"
 #include "access/twophase_rmgr.h"
 #include "commands/async.h"
 #include "pgstat.h"
@@ -29,7 +30,8 @@ const TwoPhaseCallback twophase_recover_callbacks[TWOPHASE_RM_MAX_ID + 1] =
        NULL,                                           /* Inval */
        NULL,                                           /* flat file update */
        NULL,                                           /* notify/listen */
-       NULL                                            /* pgstat */
+       NULL,                                           /* pgstat */
+       multixact_twophase_recover      /* MultiXact */
 };
 
 const TwoPhaseCallback twophase_postcommit_callbacks[TWOPHASE_RM_MAX_ID + 1] =
@@ -39,7 +41,8 @@ const TwoPhaseCallback twophase_postcommit_callbacks[TWOPHASE_RM_MAX_ID + 1] =
        inval_twophase_postcommit,      /* Inval */
        flatfile_twophase_postcommit,           /* flat file update */
        notify_twophase_postcommit, /* notify/listen */
-       pgstat_twophase_postcommit      /* pgstat */
+       pgstat_twophase_postcommit,     /* pgstat */
+       multixact_twophase_postcommit /* MultiXact */
 };
 
 const TwoPhaseCallback twophase_postabort_callbacks[TWOPHASE_RM_MAX_ID + 1] =
@@ -49,5 +52,6 @@ const TwoPhaseCallback twophase_postabort_callbacks[TWOPHASE_RM_MAX_ID + 1] =
        NULL,                                           /* Inval */
        NULL,                                           /* flat file update */
        NULL,                                           /* notify/listen */
-       pgstat_twophase_postabort       /* pgstat */
+       pgstat_twophase_postabort,      /* pgstat */
+       multixact_twophase_postabort /* MultiXact */
 };
index 673419e1742a2586830eb6e7b0ce71d02686ad18..c2d9101108b70c660e7f5fd3a4b1ae94aefe33f3 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.257.2.3 2009/03/11 00:08:06 alvherre Exp $
+ *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.257.2.4 2009/11/23 09:59:00 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1908,6 +1908,7 @@ PrepareTransaction(void)
        AtPrepare_Inval();
        AtPrepare_Locks();
        AtPrepare_PgStat();
+       AtPrepare_MultiXact();
 
        /*
         * Here is where we really truly prepare.
@@ -1958,7 +1959,7 @@ PrepareTransaction(void)
 
        PostPrepare_smgr();
 
-       AtEOXact_MultiXact();
+       PostPrepare_MultiXact(xid);
 
        PostPrepare_Locks(xid);
 
index 7cec1afe881665090f9b86b6f29bb3e8868c6653..55c700c45734a0084dea296902edd17b6d55429c 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/multixact.h,v 1.13 2008/01/01 19:45:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/multixact.h,v 1.13.2.1 2009/11/23 09:59:00 heikki Exp $
  */
 #ifndef MULTIXACT_H
 #define MULTIXACT_H
@@ -52,6 +52,8 @@ extern void MultiXactIdSetOldestMember(void);
 extern int     GetMultiXactIdMembers(MultiXactId multi, TransactionId **xids);
 
 extern void AtEOXact_MultiXact(void);
+extern void AtPrepare_MultiXact(void);
+extern void PostPrepare_MultiXact(TransactionId xid);
 
 extern Size MultiXactShmemSize(void);
 extern void MultiXactShmemInit(void);
@@ -67,6 +69,13 @@ extern void MultiXactSetNextMXact(MultiXactId nextMulti,
 extern void MultiXactAdvanceNextMXact(MultiXactId minMulti,
                                                  MultiXactOffset minMultiOffset);
 
+extern void multixact_twophase_recover(TransactionId xid, uint16 info,
+                                                  void *recdata, uint32 len);
+extern void multixact_twophase_postcommit(TransactionId xid, uint16 info,
+                                                         void *recdata, uint32 len);
+extern void multixact_twophase_postabort(TransactionId xid, uint16 info,
+                                                        void *recdata, uint32 len);
+
 extern void multixact_redo(XLogRecPtr lsn, XLogRecord *record);
 extern void multixact_desc(StringInfo buf, uint8 xl_info, char *rec);
 
index c41dcb22382d7c02cc8b67a6e4127b0c3c372345..96d4d7141b7c24c873bd4c86688c364bd5a17e6b 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/twophase.h,v 1.10 2008/01/01 19:45:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/twophase.h,v 1.10.2.1 2009/11/23 09:59:00 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,6 +15,7 @@
 #define TWOPHASE_H
 
 #include "access/xlogdefs.h"
+#include "storage/backendid.h"
 #include "storage/proc.h"
 #include "utils/timestamp.h"
 
@@ -31,6 +32,7 @@ extern Size TwoPhaseShmemSize(void);
 extern void TwoPhaseShmemInit(void);
 
 extern PGPROC *TwoPhaseGetDummyProc(TransactionId xid);
+extern BackendId TwoPhaseGetDummyBackendId(TransactionId xid);
 
 extern GlobalTransaction MarkAsPreparing(TransactionId xid, const char *gid,
                                TimestampTz prepared_at,
index e3b1bc0292cd41894546db6b13bbbdbf9e9789cb..945e25cca33d098a86d253481c693f179a8d6874 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/twophase_rmgr.h,v 1.6 2008/01/01 19:45:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/twophase_rmgr.h,v 1.6.2.1 2009/11/23 09:59:00 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,7 +27,8 @@ typedef uint8 TwoPhaseRmgrId;
 #define TWOPHASE_RM_FLATFILES_ID       3
 #define TWOPHASE_RM_NOTIFY_ID          4
 #define TWOPHASE_RM_PGSTAT_ID          5
-#define TWOPHASE_RM_MAX_ID                     TWOPHASE_RM_PGSTAT_ID
+#define TWOPHASE_RM_MULTIXACT_ID       6
+#define TWOPHASE_RM_MAX_ID                     TWOPHASE_RM_MULTIXACT_ID
 
 extern const TwoPhaseCallback twophase_recover_callbacks[];
 extern const TwoPhaseCallback twophase_postcommit_callbacks[];