summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2004-09-16 20:17:49 +0000
committerTom Lane2004-09-16 20:17:49 +0000
commite412b6ea19cf91aa2df01a2c32c2b92c8352f781 (patch)
tree350592e81e4d4bb967afff8dd38e96210c1e2c0b
parent135e866c6dc7783282db0676116453ffc20a27db (diff)
Add some marginal tweaks to eliminate memory leakages associated with
subtransactions. Trivial subxacts (such as a plpgsql exception block containing no database access) now demonstrably leak zero bytes.
-rw-r--r--src/backend/access/transam/xact.c55
-rw-r--r--src/backend/executor/spi.c21
-rw-r--r--src/backend/utils/mmgr/aset.c22
-rw-r--r--src/backend/utils/mmgr/mcxt.c22
-rw-r--r--src/include/nodes/memnodes.h1
-rw-r--r--src/include/utils/memutils.h1
6 files changed, 112 insertions, 10 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index b4d090b31b..0ff2216b53 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -861,9 +861,6 @@ AtCommit_Memory(void)
/*
* AtSubCommit_Memory
- *
- * We do not throw away the child's CurTransactionContext, since the data
- * it contains will be needed at upper commit.
*/
static void
AtSubCommit_Memory(void)
@@ -875,6 +872,18 @@ AtSubCommit_Memory(void)
/* Return to parent transaction level's memory context. */
CurTransactionContext = s->parent->curTransactionContext;
MemoryContextSwitchTo(CurTransactionContext);
+
+ /*
+ * Ordinarily we cannot throw away the child's CurTransactionContext,
+ * since the data it contains will be needed at upper commit. However,
+ * if there isn't actually anything in it, we can throw it away. This
+ * avoids a small memory leak in the common case of "trivial" subxacts.
+ */
+ if (MemoryContextIsEmpty(s->curTransactionContext))
+ {
+ MemoryContextDelete(s->curTransactionContext);
+ s->curTransactionContext = NULL;
+ }
}
/*
@@ -890,13 +899,27 @@ AtSubCommit_childXids(void)
Assert(s->parent != NULL);
- old_cxt = MemoryContextSwitchTo(s->parent->curTransactionContext);
+ /*
+ * 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.
+ */
+ old_cxt = MemoryContextSwitchTo(TopTransactionContext);
s->parent->childXids = lappend_xid(s->parent->childXids,
s->transactionId);
- s->parent->childXids = list_concat(s->parent->childXids, s->childXids);
- s->childXids = NIL; /* ensure list not doubly referenced */
+ if (s->childXids != NIL)
+ {
+ s->parent->childXids = list_concat(s->parent->childXids,
+ s->childXids);
+ /*
+ * list_concat doesn't free the list header for the second list;
+ * do so here to avoid memory leakage (kluge)
+ */
+ pfree(s->childXids);
+ s->childXids = NIL;
+ }
MemoryContextSwitchTo(old_cxt);
}
@@ -1093,6 +1116,23 @@ AtSubAbort_Memory(void)
}
/*
+ * AtSubAbort_childXids
+ */
+static void
+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
+ * explicitly at abort to avoid leakage.
+ */
+ list_free(s->childXids);
+ s->childXids = NIL;
+}
+
+/*
* RecordSubTransactionAbort
*/
static void
@@ -3317,7 +3357,10 @@ AbortSubTransaction(void)
/* Advertise the fact that we aborted in pg_clog. */
if (TransactionIdIsValid(s->transactionId))
+ {
RecordSubTransactionAbort();
+ AtSubAbort_childXids();
+ }
/* Post-abort cleanup */
CallSubXactCallbacks(SUBXACT_EVENT_ABORT_SUB, s->subTransactionId,
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 478033bfd4..70afa45e5e 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -104,6 +104,8 @@ SPI_connect(void)
_SPI_current = &(_SPI_stack[_SPI_connected]);
_SPI_current->processed = 0;
_SPI_current->tuptable = NULL;
+ _SPI_current->procCxt = NULL; /* in case we fail to create 'em */
+ _SPI_current->execCxt = NULL;
_SPI_current->connectSubid = GetCurrentSubTransactionId();
/*
@@ -144,7 +146,9 @@ SPI_finish(void)
/* Release memory used in procedure call */
MemoryContextDelete(_SPI_current->execCxt);
+ _SPI_current->execCxt = NULL;
MemoryContextDelete(_SPI_current->procCxt);
+ _SPI_current->procCxt = NULL;
/*
* Reset result variables, especially SPI_tuptable which is probably
@@ -215,10 +219,23 @@ AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
found = true;
/*
+ * Release procedure memory explicitly (see note in SPI_connect)
+ */
+ if (connection->execCxt)
+ {
+ MemoryContextDelete(connection->execCxt);
+ connection->execCxt = NULL;
+ }
+ if (connection->procCxt)
+ {
+ MemoryContextDelete(connection->procCxt);
+ connection->procCxt = NULL;
+ }
+
+ /*
* Pop the stack entry and reset global variables. Unlike
* SPI_finish(), we don't risk switching to memory contexts that
- * might be already gone, or deleting memory contexts that have
- * been or will be thrown away anyway.
+ * might be already gone.
*/
_SPI_connected--;
_SPI_curid = _SPI_connected;
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 1c749f930a..87d5460de9 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -205,6 +205,7 @@ static void AllocSetInit(MemoryContext context);
static void AllocSetReset(MemoryContext context);
static void AllocSetDelete(MemoryContext context);
static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer);
+static bool AllocSetIsEmpty(MemoryContext context);
static void AllocSetStats(MemoryContext context);
#ifdef MEMORY_CONTEXT_CHECKING
@@ -222,6 +223,7 @@ static MemoryContextMethods AllocSetMethods = {
AllocSetReset,
AllocSetDelete,
AllocSetGetChunkSpace,
+ AllocSetIsEmpty,
AllocSetStats
#ifdef MEMORY_CONTEXT_CHECKING
,AllocSetCheck
@@ -992,6 +994,26 @@ AllocSetGetChunkSpace(MemoryContext context, void *pointer)
}
/*
+ * AllocSetIsEmpty
+ * Is an allocset empty of any allocated space?
+ */
+static bool
+AllocSetIsEmpty(MemoryContext context)
+{
+ AllocSet set = (AllocSet) context;
+
+ /*
+ * For now, we say "empty" only if the context never contained any
+ * space at all. We could examine the freelists to determine if all
+ * space has been freed, but it's not really worth the trouble for
+ * present uses of this functionality.
+ */
+ if (set->blocks == NULL)
+ return true;
+ return false;
+}
+
+/*
* AllocSetStats
* Displays stats about memory consumption of an allocset.
*/
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 09c0e7f7e1..d843996c80 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -292,6 +292,25 @@ GetMemoryChunkContext(void *pointer)
}
/*
+ * MemoryContextIsEmpty
+ * Is a memory context empty of any allocated space?
+ */
+bool
+MemoryContextIsEmpty(MemoryContext context)
+{
+ AssertArg(MemoryContextIsValid(context));
+
+ /*
+ * For now, we consider a memory context nonempty if it has any children;
+ * perhaps this should be changed later.
+ */
+ if (context->firstchild != NULL)
+ return false;
+ /* Otherwise use the type-specific inquiry */
+ return (*context->methods->is_empty) (context);
+}
+
+/*
* MemoryContextStats
* Print statistics about the named context and all its descendants.
*
@@ -662,7 +681,6 @@ void
pgport_pfree(void *pointer)
{
pfree(pointer);
- return;
}
-#endif
+#endif /* WIN32 */
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index f6aa31d9bc..52f107ffa4 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -43,6 +43,7 @@ typedef struct MemoryContextMethods
void (*reset) (MemoryContext context);
void (*delete) (MemoryContext context);
Size (*get_chunk_space) (MemoryContext context, void *pointer);
+ bool (*is_empty) (MemoryContext context);
void (*stats) (MemoryContext context);
#ifdef MEMORY_CONTEXT_CHECKING
void (*check) (MemoryContext context);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index aa27b229bc..da84af5fa9 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -91,6 +91,7 @@ extern void MemoryContextDeleteChildren(MemoryContext context);
extern void MemoryContextResetAndDeleteChildren(MemoryContext context);
extern Size GetMemoryChunkSpace(void *pointer);
extern MemoryContext GetMemoryChunkContext(void *pointer);
+extern bool MemoryContextIsEmpty(MemoryContext context);
extern void MemoryContextStats(MemoryContext context);
#ifdef MEMORY_CONTEXT_CHECKING