summaryrefslogtreecommitdiff
path: root/src/backend/storage
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2004-07-01 00:52:04 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2004-07-01 00:52:04 +0000
commit573a71a5da70d6e2503c8f53e3b4f26b3b6d738d (patch)
tree070f677b0043631518f83ce84ff201bf8fda700f /src/backend/storage
parent4c9aa572fa2ee60e8ac557b866eccc7310df0a09 (diff)
downloadpostgresql-573a71a5da70d6e2503c8f53e3b4f26b3b6d738d.tar.gz
Nested transactions. There is still much left to do, especially on the
performance front, but with feature freeze upon us I think it's time to drive a stake in the ground and say that this will be in 7.5. Alvaro Herrera, with some help from Tom Lane.
Diffstat (limited to 'src/backend/storage')
-rw-r--r--src/backend/storage/buffer/bufmgr.c119
-rw-r--r--src/backend/storage/ipc/ipci.c8
-rw-r--r--src/backend/storage/ipc/sinval.c93
-rw-r--r--src/backend/storage/lmgr/lmgr.c20
-rw-r--r--src/backend/storage/lmgr/lock.c45
-rw-r--r--src/backend/storage/lmgr/lwlock.c6
-rw-r--r--src/backend/storage/lmgr/proc.c34
-rw-r--r--src/backend/storage/smgr/smgr.c115
8 files changed, 357 insertions, 83 deletions
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 725b79cad3..4a9ddc3243 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/buffer/bufmgr.c,v 1.171 2004/06/18 06:13:33 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/buffer/bufmgr.c,v 1.172 2004/07/01 00:50:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -45,6 +45,7 @@
#include "storage/bufpage.h"
#include "storage/proc.h"
#include "storage/smgr.h"
+#include "utils/memutils.h"
#include "utils/relcache.h"
#include "pgstat.h"
@@ -64,9 +65,13 @@ long NDirectFileRead; /* some I/O's are direct file access.
* bypass bufmgr */
long NDirectFileWrite; /* e.g., I/O in psort and hashjoin. */
+/* List of upper-level-transaction buffer refcount arrays */
+static List *upperRefCounts = NIL;
+
static void PinBuffer(BufferDesc *buf);
static void UnpinBuffer(BufferDesc *buf);
+static void BufferFixLeak(Buffer bufnum, int32 shouldBe, bool emitWarning);
static void WaitIO(BufferDesc *buf);
static void StartBufferIO(BufferDesc *buf, bool forInput);
static void TerminateBufferIO(BufferDesc *buf, int err_flag);
@@ -826,31 +831,105 @@ AtEOXact_Buffers(bool isCommit)
for (i = 0; i < NBuffers; i++)
{
if (PrivateRefCount[i] != 0)
- {
- BufferDesc *buf = &(BufferDescriptors[i]);
-
- if (isCommit)
- elog(WARNING,
- "buffer refcount leak: [%03d] "
- "(rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d)",
- i,
- buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
- buf->tag.rnode.relNode,
- buf->tag.blockNum, buf->flags,
- buf->refcount, PrivateRefCount[i]);
-
- PrivateRefCount[i] = 1; /* make sure we release shared pin */
- LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
- UnpinBuffer(buf);
- LWLockRelease(BufMgrLock);
- Assert(PrivateRefCount[i] == 0);
- }
+ BufferFixLeak(i, 0, isCommit);
}
AtEOXact_LocalBuffers(isCommit);
}
/*
+ * During subtransaction start, save buffer reference counts.
+ */
+void
+AtSubStart_Buffers(void)
+{
+ int32 *copyRefCounts;
+ Size rcSize;
+ MemoryContext old_cxt;
+
+ /* this is probably the active context already, but be safe */
+ old_cxt = MemoryContextSwitchTo(CurTransactionContext);
+
+ /*
+ * We need to copy the current state of PrivateRefCount[]. In the typical
+ * scenario, few if any of the entries will be nonzero, and we could save
+ * space by storing only the nonzero ones. However, copying the whole
+ * thing is lots simpler and faster both here and in AtEOSubXact_Buffers,
+ * so it seems best to waste the space.
+ */
+ rcSize = NBuffers * sizeof(int32);
+ copyRefCounts = (int32 *) palloc(rcSize);
+ memcpy(copyRefCounts, PrivateRefCount, rcSize);
+
+ /* Attach to list */
+ upperRefCounts = lcons(copyRefCounts, upperRefCounts);
+
+ MemoryContextSwitchTo(old_cxt);
+}
+
+/*
+ * AtEOSubXact_Buffers
+ *
+ * At subtransaction end, we restore the saved counts. If committing, we
+ * complain if the refcounts don't match; if aborting, just restore silently.
+ */
+void
+AtEOSubXact_Buffers(bool isCommit)
+{
+ int32 *oldRefCounts;
+ int i;
+
+ oldRefCounts = (int32 *) linitial(upperRefCounts);
+ upperRefCounts = list_delete_first(upperRefCounts);
+
+ for (i = 0; i < NBuffers; i++)
+ {
+ if (PrivateRefCount[i] != oldRefCounts[i])
+ BufferFixLeak(i, oldRefCounts[i], isCommit);
+ }
+
+ pfree(oldRefCounts);
+}
+
+/*
+ * Fix a buffer refcount leak.
+ *
+ * The caller does not hold the BufMgrLock.
+ */
+static void
+BufferFixLeak(Buffer bufnum, int32 shouldBe, bool emitWarning)
+{
+ BufferDesc *buf = &(BufferDescriptors[bufnum]);
+
+ if (emitWarning)
+ elog(WARNING,
+ "buffer refcount leak: [%03d] (rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d, should be=%d)",
+ bufnum,
+ buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
+ buf->tag.rnode.relNode,
+ buf->tag.blockNum, buf->flags,
+ buf->refcount, PrivateRefCount[bufnum], shouldBe);
+
+ /* If it's less, we're in a heap o' trouble */
+ if (PrivateRefCount[bufnum] <= shouldBe)
+ elog(FATAL, "buffer refcount was decreased by subtransaction");
+
+ if (shouldBe > 0)
+ {
+ /* We still keep the shared-memory pin */
+ PrivateRefCount[bufnum] = shouldBe;
+ }
+ else
+ {
+ PrivateRefCount[bufnum] = 1; /* make sure we release shared pin */
+ LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
+ UnpinBuffer(buf);
+ LWLockRelease(BufMgrLock);
+ Assert(PrivateRefCount[bufnum] == 0);
+ }
+}
+
+/*
* FlushBufferPool
*
* Flush all dirty blocks in buffer pool to disk at the checkpoint time.
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 69c460306b..4c759db9d8 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -8,16 +8,16 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/ipc/ipci.c,v 1.68 2004/05/29 22:48:20 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/ipc/ipci.c,v 1.69 2004/07/01 00:50:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-
-#include "miscadmin.h"
#include "access/clog.h"
+#include "access/subtrans.h"
#include "access/xlog.h"
+#include "miscadmin.h"
#include "postmaster/bgwriter.h"
#include "storage/bufmgr.h"
#include "storage/freespace.h"
@@ -70,6 +70,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate,
size += LockShmemSize(maxBackends);
size += XLOGShmemSize();
size += CLOGShmemSize();
+ size += SUBTRANSShmemSize();
size += LWLockShmemSize();
size += SInvalShmemSize(maxBackends);
size += FreeSpaceShmemSize();
@@ -133,6 +134,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate,
*/
XLOGShmemInit();
CLOGShmemInit();
+ SUBTRANSShmemInit();
InitBufferPool();
/*
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 856d0f0a73..bf4eb0f629 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/ipc/sinval.c,v 1.64 2004/06/02 21:29:28 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/ipc/sinval.c,v 1.65 2004/07/01 00:50:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,6 +16,8 @@
#include <signal.h>
+#include "access/subtrans.h"
+#include "access/transam.h"
#include "commands/async.h"
#include "storage/ipc.h"
#include "storage/proc.h"
@@ -428,20 +430,40 @@ DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself)
/*
* TransactionIdIsInProgress -- is given transaction running by some backend
+ *
+ * There are three possibilities for finding a running transaction:
+ *
+ * 1. the given Xid is a main transaction Id. We will find this out cheaply
+ * by looking at the PGPROC struct for each backend.
+ *
+ * 2. the given Xid is one of the cached subxact Xids in the PGPROC array.
+ * We can find this out cheaply too.
+ *
+ * 3. Search the SubTrans tree. This is the slowest, but sadly it has to be
+ * done always if the other two failed.
+ *
+ * SInvalLock has to be held while we do 1 and 2. If we save all the Xids
+ * while doing 1, we can release the SInvalLock while we do 3. This buys back
+ * some concurrency (we can't retrieve the main Xids from PGPROC again anyway,
+ * see GetNewTransactionId)
*/
bool
TransactionIdIsInProgress(TransactionId xid)
{
- bool result = false;
- SISeg *segP = shmInvalBuffer;
- ProcState *stateP = segP->procState;
- int index;
+ bool result = false;
+ SISeg *segP = shmInvalBuffer;
+ ProcState *stateP = segP->procState;
+ int i;
+ int nxids = 0;
+ TransactionId *xids;
+
+ xids = (TransactionId *)palloc(sizeof(TransactionId) * segP->maxBackends);
LWLockAcquire(SInvalLock, LW_SHARED);
- for (index = 0; index < segP->lastBackend; index++)
+ for (i = 0; i < segP->lastBackend; i++)
{
- SHMEM_OFFSET pOffset = stateP[index].procStruct;
+ SHMEM_OFFSET pOffset = stateP[i].procStruct;
if (pOffset != INVALID_OFFSET)
{
@@ -450,16 +472,71 @@ TransactionIdIsInProgress(TransactionId xid)
/* Fetch xid just once - see GetNewTransactionId */
TransactionId pxid = proc->xid;
+ /*
+ * check the main Xid (step 1 above)
+ */
if (TransactionIdEquals(pxid, xid))
{
result = true;
break;
}
+
+ /*
+ * save the main Xid for step 3.
+ */
+ xids[nxids++] = pxid;
+
+#ifdef NOT_USED
+ FIXME -- waiting to save the Xids in PGPROC ...
+
+ /*
+ * check the saved Xids array (step 2)
+ */
+ for (j = 0; j < PGPROC_MAX_SAVED_XIDS; j++)
+ {
+ pxid = proc->savedxids[j];
+
+ if (!TransactionIdIsValid(pxids))
+ break;
+
+ if (TransactionIdEquals(pxid, xid))
+ {
+ result = true;
+ break;
+ }
+ }
+#endif
+
+ if (result)
+ break;
+
}
}
LWLockRelease(SInvalLock);
+ /*
+ * Step 3: have to check pg_subtrans. Use the saved Xids.
+ *
+ * XXX Could save the cached Xids too for further improvement.
+ */
+ if (!result)
+ {
+ /* this is a potentially expensive call. */
+ xid = SubTransGetTopmostTransaction(xid);
+
+ Assert(TransactionIdIsValid(xid));
+
+ /*
+ * We don't care if it aborted, because if it did, we won't find
+ * it in the array.
+ */
+
+ for (i = 0; i < nxids; i++)
+ if (TransactionIdEquals(xids[i], xid))
+ return true;
+ }
+
return result;
}
@@ -596,7 +673,7 @@ GetSnapshotData(Snapshot snapshot, bool serializable)
* This does open a possibility for avoiding repeated malloc/free:
* since MaxBackends does not change at runtime, we can simply reuse
* the previous xip array if any. (This relies on the fact that all
- * calls pass static SnapshotData structs.)
+ * callers pass static SnapshotData structs.)
*/
if (snapshot->xip == NULL)
{
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index e4e52b16ab..45305b4dea 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -8,13 +8,14 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.63 2004/05/28 05:13:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.64 2004/07/01 00:50:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/subtrans.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
@@ -333,19 +334,21 @@ XactLockTableInsert(TransactionId xid)
* XactLockTableWait
*
* Wait for the specified transaction to commit or abort.
+ * We actually wait on the topmost transaction of the transaction tree.
*/
void
XactLockTableWait(TransactionId xid)
{
LOCKTAG tag;
TransactionId myxid = GetCurrentTransactionId();
+ TransactionId waitXid = SubTransGetTopmostTransaction(xid);
- Assert(!TransactionIdEquals(xid, myxid));
+ Assert(!SubTransXidsHaveCommonAncestor(waitXid, myxid));
MemSet(&tag, 0, sizeof(tag));
tag.relId = XactLockTableId;
tag.dbId = InvalidOid;
- tag.objId.xid = xid;
+ tag.objId.xid = waitXid;
if (!LockAcquire(LockTableId, &tag, myxid,
ShareLock, false))
@@ -355,8 +358,13 @@ XactLockTableWait(TransactionId xid)
/*
* Transaction was committed/aborted/crashed - we have to update
- * pg_clog if transaction is still marked as running.
+ * pg_clog if transaction is still marked as running. If it's a
+ * subtransaction, we can update the parent status too.
*/
- if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid))
- TransactionIdAbort(xid);
+ if (!TransactionIdDidCommit(waitXid) && !TransactionIdDidAbort(waitXid))
+ {
+ TransactionIdAbort(waitXid);
+ if (waitXid != xid)
+ TransactionIdAbort(xid);
+ }
}
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index c04f3b5c88..6b7f43440e 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/lmgr/lock.c,v 1.133 2004/06/05 19:48:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/lmgr/lock.c,v 1.134 2004/07/01 00:50:59 tgl Exp $
*
* NOTES
* Outside modules can create a lock table and acquire/release
@@ -23,7 +23,7 @@
* Interface:
*
* LockAcquire(), LockRelease(), LockMethodTableInit(),
- * LockMethodTableRename(), LockReleaseAll,
+ * LockMethodTableRename(), LockReleaseAll(),
* LockCheckConflicts(), GrantLock()
*
*-------------------------------------------------------------------------
@@ -1129,19 +1129,25 @@ LockRelease(LOCKMETHODID lockmethodid, LOCKTAG *locktag,
}
/*
- * LockReleaseAll -- Release all locks in a process's lock list.
+ * LockReleaseAll -- Release all locks of the specified lock method that
+ * are held by the specified process.
*
- * Well, not really *all* locks.
+ * Well, not necessarily *all* locks. The available behaviors are:
*
- * If 'allxids' is TRUE, all locks of the specified lock method are
- * released, regardless of transaction affiliation.
+ * which == ReleaseAll: release all locks regardless of transaction
+ * affiliation.
*
- * If 'allxids' is FALSE, all locks of the specified lock method and
- * specified XID are released.
+ * which == ReleaseAllExceptSession: release all locks with Xid != 0
+ * (zero is the Xid used for "session" locks).
+ *
+ * which == ReleaseGivenXids: release only locks whose Xids appear in
+ * the xids[] array (of length nxids).
+ *
+ * xids/nxids are ignored when which != ReleaseGivenXids.
*/
bool
LockReleaseAll(LOCKMETHODID lockmethodid, PGPROC *proc,
- bool allxids, TransactionId xid)
+ LockReleaseWhich which, int nxids, TransactionId *xids)
{
SHM_QUEUE *procHolders = &(proc->procHolders);
PROCLOCK *proclock;
@@ -1190,8 +1196,25 @@ LockReleaseAll(LOCKMETHODID lockmethodid, PGPROC *proc,
if (LOCK_LOCKMETHOD(*lock) != lockmethodid)
goto next_item;
- /* If not allxids, ignore items that are of the wrong xid */
- if (!allxids && !TransactionIdEquals(xid, proclock->tag.xid))
+ if (which == ReleaseGivenXids)
+ {
+ /* Ignore locks with an Xid not in the list */
+ bool release = false;
+
+ for (i = 0; i < nxids; i++)
+ {
+ if (TransactionIdEquals(proclock->tag.xid, xids[i]))
+ {
+ release = true;
+ break;
+ }
+ }
+ if (!release)
+ goto next_item;
+ }
+ /* Ignore locks with Xid=0 unless we are asked to release All locks */
+ else if (TransactionIdEquals(proclock->tag.xid, InvalidTransactionId)
+ && which != ReleaseAll)
goto next_item;
PROCLOCK_PRINT("LockReleaseAll", proclock);
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index d1410d04a0..e48531c10a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -15,13 +15,14 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/lmgr/lwlock.c,v 1.20 2004/06/11 16:43:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/lmgr/lwlock.c,v 1.21 2004/07/01 00:50:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/clog.h"
+#include "access/subtrans.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
#include "storage/spin.h"
@@ -111,6 +112,9 @@ NumLWLocks(void)
/* clog.c needs one per CLOG buffer + one control lock */
numLocks += NUM_CLOG_BUFFERS + 1;
+ /* subtrans.c needs one per SubTrans buffer + one control lock */
+ numLocks += NUM_SUBTRANS_BUFFERS + 1;
+
/* Perhaps create a few more for use by user-defined modules? */
return numLocks;
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index dbf5b41415..abe44e808a 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.148 2004/05/29 22:48:20 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.149 2004/07/01 00:50:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -380,26 +380,34 @@ LockWaitCancel(void)
/*
* ProcReleaseLocks() -- release locks associated with current transaction
- * at transaction commit or abort
+ * at main transaction and subtransaction commit or abort
*
- * At commit, we release only locks tagged with the current transaction's XID,
- * leaving those marked with XID 0 (ie, session locks) undisturbed. At abort,
- * we release all locks including XID 0, because we need to clean up after
- * a failure. This logic will need extension if we ever support nested
- * transactions.
+ * The options for which locks to release are the same as for the underlying
+ * LockReleaseAll() function.
*
- * Note that user locks are not released in either case.
+ * Notes:
+ *
+ * At main transaction commit, we release all locks except session locks.
+ * At main transaction abort, we release all locks including session locks;
+ * this lets us clean up after a VACUUM FULL failure.
+ *
+ * At subtransaction commit, we don't release any locks (so this func is not
+ * called at all); we will defer the releasing to the parent transaction.
+ * At subtransaction abort, we release all locks held by the subtransaction;
+ * this is implemented by passing in the Xids of the failed subxact and its
+ * children in the xids[] array.
+ *
+ * Note that user locks are not released in any case.
*/
void
-ProcReleaseLocks(bool isCommit)
+ProcReleaseLocks(LockReleaseWhich which, int nxids, TransactionId *xids)
{
if (!MyProc)
return;
/* If waiting, get off wait queue (should only be needed after error) */
LockWaitCancel();
/* Release locks */
- LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc,
- !isCommit, GetCurrentTransactionId());
+ LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc, which, nxids, xids);
}
@@ -432,11 +440,11 @@ ProcKill(int code, Datum arg)
LockWaitCancel();
/* Remove from the standard lock table */
- LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc, true, InvalidTransactionId);
+ LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc, ReleaseAll, 0, NULL);
#ifdef USER_LOCKS
/* Remove from the user lock table */
- LockReleaseAll(USER_LOCKMETHOD, MyProc, true, InvalidTransactionId);
+ LockReleaseAll(USER_LOCKMETHOD, MyProc, ReleaseAll, 0, NULL);
#endif
SpinLockAcquire(ProcStructLock);
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index c7783d878f..5c53d48f83 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/smgr/smgr.c,v 1.74 2004/06/18 06:13:37 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/smgr/smgr.c,v 1.75 2004/07/01 00:51:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -80,9 +80,10 @@ static HTAB *SMgrRelationHash = NULL;
* executed immediately, but is just entered in the list. When and if
* the transaction commits, we can delete the physical file.
*
- * NOTE: the list is kept in TopMemoryContext to be sure it won't disappear
- * unbetimes. It'd probably be OK to keep it in TopTransactionContext,
- * but I'm being paranoid.
+ * The list is kept in CurTransactionContext. In subtransactions, each
+ * subtransaction has its own list in its own CurTransactionContext, but
+ * successful subtransactions attach their lists to their parent's list.
+ * Failed subtransactions can immediately execute the abort-time actions.
*/
typedef struct PendingRelDelete
@@ -91,10 +92,11 @@ typedef struct PendingRelDelete
int which; /* which storage manager? */
bool isTemp; /* is it a temporary relation? */
bool atCommit; /* T=delete at commit; F=delete at abort */
- struct PendingRelDelete *next; /* linked-list link */
} PendingRelDelete;
-static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
+static List *pendingDeletes = NIL; /* head of linked list */
+
+static List *upperPendingDeletes = NIL; /* list of upper-xact lists */
/*
@@ -305,6 +307,7 @@ smgrcreate(SMgrRelation reln, bool isTemp, bool isRedo)
XLogRecData rdata;
xl_smgr_create xlrec;
PendingRelDelete *pending;
+ MemoryContext old_cxt;
if (! (*(smgrsw[reln->smgr_which].smgr_create)) (reln, isRedo))
ereport(ERROR,
@@ -332,14 +335,17 @@ smgrcreate(SMgrRelation reln, bool isTemp, bool isRedo)
lsn = XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLOG_NO_TRAN, &rdata);
/* Add the relation to the list of stuff to delete at abort */
- pending = (PendingRelDelete *)
- MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
+ old_cxt = MemoryContextSwitchTo(CurTransactionContext);
+
+ pending = (PendingRelDelete *) palloc(sizeof(PendingRelDelete));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = false; /* delete if abort */
- pending->next = pendingDeletes;
- pendingDeletes = pending;
+
+ pendingDeletes = lcons(pending, pendingDeletes);
+
+ MemoryContextSwitchTo(old_cxt);
}
/*
@@ -354,16 +360,20 @@ void
smgrscheduleunlink(SMgrRelation reln, bool isTemp)
{
PendingRelDelete *pending;
+ MemoryContext old_cxt;
/* Add the relation to the list of stuff to delete at commit */
- pending = (PendingRelDelete *)
- MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
+ old_cxt = MemoryContextSwitchTo(CurTransactionContext);
+
+ pending = (PendingRelDelete *) palloc(sizeof(PendingRelDelete));
pending->relnode = reln->smgr_rnode;
pending->which = reln->smgr_which;
pending->isTemp = isTemp;
pending->atCommit = true; /* delete if commit */
- pending->next = pendingDeletes;
- pendingDeletes = pending;
+
+ pendingDeletes = lcons(pending, pendingDeletes);
+
+ MemoryContextSwitchTo(old_cxt);
/*
* NOTE: if the relation was created in this transaction, it will now
@@ -627,18 +637,21 @@ smgrimmedsync(SMgrRelation reln)
void
smgrDoPendingDeletes(bool isCommit)
{
- while (pendingDeletes != NULL)
+ ListCell *p;
+
+ foreach(p, pendingDeletes)
{
- PendingRelDelete *pending = pendingDeletes;
+ PendingRelDelete *pending = lfirst(p);
- pendingDeletes = pending->next;
if (pending->atCommit == isCommit)
smgr_internal_unlink(pending->relnode,
pending->which,
pending->isTemp,
false);
- pfree(pending);
}
+
+ /* We needn't free the cells since they are in CurTransactionContext */
+ pendingDeletes = NIL;
}
/*
@@ -647,17 +660,22 @@ smgrDoPendingDeletes(bool isCommit)
* The return value is the number of relations scheduled for termination.
* *ptr is set to point to a freshly-palloc'd array of RelFileNodes.
* If there are no relations to be deleted, *ptr is set to NULL.
+ *
+ * Note that the list does not include anything scheduled for termination
+ * by upper-level transactions.
*/
int
smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
{
int nrels;
RelFileNode *rptr;
- PendingRelDelete *pending;
+ ListCell *p;
nrels = 0;
- for (pending = pendingDeletes; pending != NULL; pending = pending->next)
+ foreach(p, pendingDeletes)
{
+ PendingRelDelete *pending = lfirst(p);
+
if (pending->atCommit == forCommit)
nrels++;
}
@@ -668,8 +686,10 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
}
rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode));
*ptr = rptr;
- for (pending = pendingDeletes; pending != NULL; pending = pending->next)
+ foreach(p, pendingDeletes)
{
+ PendingRelDelete *pending = lfirst(p);
+
if (pending->atCommit == forCommit)
*rptr++ = pending->relnode;
}
@@ -677,6 +697,59 @@ smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr)
}
/*
+ * AtSubStart_smgr() --- Take care of subtransaction start.
+ *
+ * Push empty state for the new subtransaction.
+ */
+void
+AtSubStart_smgr(void)
+{
+ MemoryContext old_cxt;
+
+ /* Keep the list-of-lists in TopTransactionContext for simplicity */
+ old_cxt = MemoryContextSwitchTo(TopTransactionContext);
+
+ upperPendingDeletes = lcons(pendingDeletes, upperPendingDeletes);
+
+ pendingDeletes = NIL;
+
+ MemoryContextSwitchTo(old_cxt);
+}
+
+/*
+ * AtSubCommit_smgr() --- Take care of subtransaction commit.
+ *
+ * Reassign all items in the pending deletes list to the parent transaction.
+ */
+void
+AtSubCommit_smgr(void)
+{
+ List *parentPendingDeletes;
+
+ parentPendingDeletes = (List *) linitial(upperPendingDeletes);
+ upperPendingDeletes = list_delete_first(upperPendingDeletes);
+
+ pendingDeletes = list_concat(parentPendingDeletes, pendingDeletes);
+}
+
+/*
+ * AtSubAbort_smgr() --- Take care of subtransaction abort.
+ *
+ * Delete created relations and forget about deleted relations.
+ * We can execute these operations immediately because we know this
+ * subtransaction will not commit.
+ */
+void
+AtSubAbort_smgr(void)
+{
+ smgrDoPendingDeletes(false);
+
+ /* Must pop the stack, too */
+ pendingDeletes = (List *) linitial(upperPendingDeletes);
+ upperPendingDeletes = list_delete_first(upperPendingDeletes);
+}
+
+/*
* smgrcommit() -- Prepare to commit changes made during the current
* transaction.
*