X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Faccess%2Ftransam%2Fxact.c;h=9af53a5953f09e2b2726fc7efa8ad4fcc1f2b9ab;hb=a7b7b07af340c73adee9959edf260695591a9496;hp=13c3a0378b143a8b7d9e2044f49fc712b692df61;hpb=ac12412edec900fea429d5b9bf9e58990d9f4533;p=postgresql diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 13c3a0378b..9af53a5953 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.257 2008/01/15 18:56:59 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.260 2008/03/17 19:44:41 petere Exp $ * *------------------------------------------------------------------------- */ @@ -46,6 +46,7 @@ #include "utils/memutils.h" #include "utils/relcache.h" #include "utils/xml.h" +#include "pg_trace.h" /* @@ -62,6 +63,13 @@ bool XactSyncCommit = true; int CommitDelay = 0; /* precommit delay in microseconds */ int CommitSiblings = 5; /* # concurrent xacts needed to sleep */ +/* + * MyXactAccessedTempRel is set when a temporary relation is accessed. + * We don't allow PREPARE TRANSACTION in that case. (This is global + * so that it can be set from heapam.c.) + */ +bool MyXactAccessedTempRel = false; + /* * transaction states - transaction state from server perspective @@ -123,7 +131,9 @@ typedef struct TransactionStateData int gucNestLevel; /* GUC context nesting depth */ MemoryContext curTransactionContext; /* my xact-lifetime context */ ResourceOwner curTransactionOwner; /* my query resources */ - List *childXids; /* subcommitted child XIDs */ + TransactionId *childXids; /* subcommitted child XIDs, in XID order */ + int nChildXids; /* # of subcommitted child XIDs */ + int maxChildXids; /* allocated size of childXids[] */ Oid prevUser; /* previous CurrentUserId setting */ bool prevSecDefCxt; /* previous SecurityDefinerContext setting */ bool prevXactReadOnly; /* entry-time xact r/o state */ @@ -149,7 +159,9 @@ static TransactionStateData TopTransactionStateData = { 0, /* GUC context nesting depth */ NULL, /* cur transaction context */ NULL, /* cur transaction resource owner */ - NIL, /* subcommitted child Xids */ + NULL, /* subcommitted child Xids */ + 0, /* # of subcommitted child Xids */ + 0, /* allocated size of childXids[] */ InvalidOid, /* previous CurrentUserId setting */ false, /* previous SecurityDefinerContext setting */ false, /* entry-time xact r/o state */ @@ -552,7 +564,7 @@ TransactionIdIsCurrentTransactionId(TransactionId xid) */ for (s = CurrentTransactionState; s != NULL; s = s->parent) { - ListCell *cell; + int low, high; if (s->state == TRANS_ABORT) continue; @@ -560,10 +572,22 @@ TransactionIdIsCurrentTransactionId(TransactionId xid) continue; /* it can't have any child XIDs either */ if (TransactionIdEquals(xid, s->transactionId)) return true; - foreach(cell, s->childXids) + /* As the childXids array is ordered, we can use binary search */ + low = 0; + high = s->nChildXids - 1; + while (low <= high) { - if (TransactionIdEquals(xid, lfirst_xid(cell))) + int middle; + TransactionId probe; + + middle = low + (high - low) / 2; + probe = s->childXids[middle]; + if (TransactionIdEquals(probe, xid)) return true; + else if (TransactionIdPrecedes(probe, xid)) + low = middle + 1; + else + high = middle - 1; } } @@ -978,8 +1002,6 @@ cleanup: /* Clean up local data */ if (rels) pfree(rels); - if (children) - pfree(children); return latestXid; } @@ -1060,34 +1082,79 @@ static void AtSubCommit_childXids(void) { TransactionState s = CurrentTransactionState; - MemoryContext old_cxt; + int new_nChildXids; Assert(s->parent != NULL); /* - * We keep the child-XID lists in TopTransactionContext; this avoids - * setting up child-transaction contexts for what might be just a few - * bytes of grandchild XIDs. + * The parent childXids array will need to hold my XID and all my + * childXids, in addition to the XIDs already there. */ - old_cxt = MemoryContextSwitchTo(TopTransactionContext); - - s->parent->childXids = lappend_xid(s->parent->childXids, - s->transactionId); + new_nChildXids = s->parent->nChildXids + s->nChildXids + 1; - if (s->childXids != NIL) + /* Allocate or enlarge the parent array if necessary */ + if (s->parent->maxChildXids < new_nChildXids) { - s->parent->childXids = list_concat(s->parent->childXids, - s->childXids); + int new_maxChildXids; + TransactionId *new_childXids; /* - * list_concat doesn't free the list header for the second list; do so - * here to avoid memory leakage (kluge) + * Make it 2x what's needed right now, to avoid having to enlarge it + * repeatedly. But we can't go above MaxAllocSize. (The latter + * limit is what ensures that we don't need to worry about integer + * overflow here or in the calculation of new_nChildXids.) */ - pfree(s->childXids); - s->childXids = NIL; + new_maxChildXids = Min(new_nChildXids * 2, + (int) (MaxAllocSize / sizeof(TransactionId))); + + if (new_maxChildXids < new_nChildXids) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("maximum number of committed subtransactions (%d) exceeded", + (int) (MaxAllocSize / sizeof(TransactionId))))); + + /* + * We keep the child-XID arrays in TopTransactionContext; this avoids + * setting up child-transaction contexts for what might be just a few + * bytes of grandchild XIDs. + */ + if (s->parent->childXids == NULL) + new_childXids = + MemoryContextAlloc(TopTransactionContext, + new_maxChildXids * sizeof(TransactionId)); + else + new_childXids = repalloc(s->parent->childXids, + new_maxChildXids * sizeof(TransactionId)); + + s->parent->childXids = new_childXids; + s->parent->maxChildXids = new_maxChildXids; } - MemoryContextSwitchTo(old_cxt); + /* + * Copy all my XIDs to parent's array. + * + * Note: We rely on the fact that the XID of a child always follows that + * of its parent. By copying the XID of this subtransaction before the + * XIDs of its children, we ensure that the array stays ordered. Likewise, + * all XIDs already in the array belong to subtransactions started and + * subcommitted before us, so their XIDs must precede ours. + */ + s->parent->childXids[s->parent->nChildXids] = s->transactionId; + + if (s->nChildXids > 0) + memcpy(&s->parent->childXids[s->parent->nChildXids + 1], + s->childXids, + s->nChildXids * sizeof(TransactionId)); + + s->parent->nChildXids = new_nChildXids; + + /* Release child's array to avoid leakage */ + if (s->childXids != NULL) + pfree(s->childXids); + /* We must reset these to avoid double-free if fail later in commit */ + s->childXids = NULL; + s->nChildXids = 0; + s->maxChildXids = 0; } /* @@ -1252,8 +1319,6 @@ RecordTransactionAbort(bool isSubXact) /* And clean up local data */ if (rels) pfree(rels); - if (children) - pfree(children); return latestXid; } @@ -1325,12 +1390,15 @@ AtSubAbort_childXids(void) TransactionState s = CurrentTransactionState; /* - * We keep the child-XID lists in TopTransactionContext (see - * AtSubCommit_childXids). This means we'd better free the list + * We keep the child-XID arrays in TopTransactionContext (see + * AtSubCommit_childXids). This means we'd better free the array * explicitly at abort to avoid leakage. */ - list_free(s->childXids); - s->childXids = NIL; + if (s->childXids != NULL) + pfree(s->childXids); + s->childXids = NULL; + s->nChildXids = 0; + s->maxChildXids = 0; } /* ---------------------------------------------------------------- @@ -1445,6 +1513,7 @@ StartTransaction(void) XactIsoLevel = DefaultXactIsoLevel; XactReadOnly = DefaultXactReadOnly; forceSyncCommit = false; + MyXactAccessedTempRel = false; /* * reinitialize within-transaction counters @@ -1479,7 +1548,7 @@ StartTransaction(void) Assert(MyProc->backendId == vxid.backendId); MyProc->lxid = vxid.localTransactionId; - PG_TRACE1(transaction__start, vxid.localTransactionId); + TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId); /* * set transaction_timestamp() (a/k/a now()). We want this to be the same @@ -1498,7 +1567,9 @@ StartTransaction(void) */ s->nestingLevel = 1; s->gucNestLevel = 1; - s->childXids = NIL; + s->childXids = NULL; + s->nChildXids = 0; + s->maxChildXids = 0; GetUserIdAndContext(&s->prevUser, &s->prevSecDefCxt); /* SecurityDefinerContext should never be set outside a transaction */ Assert(!s->prevSecDefCxt); @@ -1604,7 +1675,7 @@ CommitTransaction(void) */ latestXid = RecordTransactionCommit(); - PG_TRACE1(transaction__commit, MyProc->lxid); + TRACE_POSTGRESQL_TRANSACTION_COMMIT(MyProc->lxid); /* * Let others know about no transaction in progress by me. Note that this @@ -1694,7 +1765,9 @@ CommitTransaction(void) s->subTransactionId = InvalidSubTransactionId; s->nestingLevel = 0; s->gucNestLevel = 0; - s->childXids = NIL; + s->childXids = NULL; + s->nChildXids = 0; + s->maxChildXids = 0; /* * done with commit processing, set current transaction state back to @@ -1770,6 +1843,26 @@ PrepareTransaction(void) /* NOTIFY and flatfiles will be handled below */ + /* + * Don't allow PREPARE TRANSACTION if we've accessed a temporary table + * in this transaction. Having the prepared xact hold locks on another + * backend's temp table seems a bad idea --- for instance it would prevent + * the backend from exiting. There are other problems too, such as how + * to clean up the source backend's local buffers and ON COMMIT state + * if the prepared xact includes a DROP of a temp table. + * + * We must check this after executing any ON COMMIT actions, because + * they might still access a temp relation. + * + * XXX In principle this could be relaxed to allow some useful special + * cases, such as a temp table created and dropped all within the + * transaction. That seems to require much more bookkeeping though. + */ + if (MyXactAccessedTempRel) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot PREPARE a transaction that has operated on temporary tables"))); + /* Prevent cancel/die interrupt while cleaning up */ HOLD_INTERRUPTS(); @@ -1903,7 +1996,9 @@ PrepareTransaction(void) s->subTransactionId = InvalidSubTransactionId; s->nestingLevel = 0; s->gucNestLevel = 0; - s->childXids = NIL; + s->childXids = NULL; + s->nChildXids = 0; + s->maxChildXids = 0; /* * done with 1st phase commit processing, set current transaction state @@ -1990,7 +2085,7 @@ AbortTransaction(void) */ latestXid = RecordTransactionAbort(false); - PG_TRACE1(transaction__abort, MyProc->lxid); + TRACE_POSTGRESQL_TRANSACTION_ABORT(MyProc->lxid); /* * Let others know about no transaction in progress by me. Note that this @@ -2073,7 +2168,9 @@ CleanupTransaction(void) s->subTransactionId = InvalidSubTransactionId; s->nestingLevel = 0; s->gucNestLevel = 0; - s->childXids = NIL; + s->childXids = NULL; + s->nChildXids = 0; + s->maxChildXids = 0; /* * done with abort processing, set current transaction state back to @@ -4023,6 +4120,19 @@ ShowTransactionState(const char *str) static void ShowTransactionStateRec(TransactionState s) { + StringInfoData buf; + + initStringInfo(&buf); + + if (s->nChildXids > 0) + { + int i; + + appendStringInfo(&buf, "%u", s->childXids[0]); + for (i = 1; i < s->nChildXids; i++) + appendStringInfo(&buf, " %u", s->childXids[i]); + } + if (s->parent) ShowTransactionStateRec(s->parent); @@ -4036,8 +4146,9 @@ ShowTransactionStateRec(TransactionState s) (unsigned int) s->subTransactionId, (unsigned int) currentCommandId, currentCommandIdUsed ? " (used)" : "", - s->nestingLevel, - nodeToString(s->childXids)))); + s->nestingLevel, buf.data))); + + pfree(buf.data); } /* @@ -4116,36 +4227,22 @@ TransStateAsString(TransState state) * xactGetCommittedChildren * * Gets the list of committed children of the current transaction. The return - * value is the number of child transactions. *children is set to point to a - * palloc'd array of TransactionIds. If there are no subxacts, *children is - * set to NULL. + * value is the number of child transactions. *ptr is set to point to an + * array of TransactionIds. The array is allocated in TopTransactionContext; + * the caller should *not* pfree() it (this is a change from pre-8.4 code!). + * If there are no subxacts, *ptr is set to NULL. */ int xactGetCommittedChildren(TransactionId **ptr) { TransactionState s = CurrentTransactionState; - int nchildren; - TransactionId *children; - ListCell *p; - nchildren = list_length(s->childXids); - if (nchildren == 0) - { + if (s->nChildXids == 0) *ptr = NULL; - return 0; - } - - children = (TransactionId *) palloc(nchildren * sizeof(TransactionId)); - *ptr = children; - - foreach(p, s->childXids) - { - TransactionId child = lfirst_xid(p); - - *children++ = child; - } + else + *ptr = s->childXids; - return nchildren; + return s->nChildXids; } /*