summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/mmgr/mcxt.c71
-rw-r--r--src/include/nodes/memnodes.h24
-rw-r--r--src/include/utils/memutils.h2
3 files changed, 85 insertions, 12 deletions
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 202bc785bcf..72a32abdddb 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -54,6 +54,7 @@ MemoryContext CurTransactionContext = NULL;
/* This is a transient link to the active portal's memory context: */
MemoryContext PortalContext = NULL;
+static void MemoryContextCallResetCallbacks(MemoryContext context);
static void MemoryContextStatsInternal(MemoryContext context, int level);
/*
@@ -115,9 +116,8 @@ MemoryContextInit(void)
* where retained memory in a context is *essential* --- we want to be
* sure ErrorContext still has some memory even if we've run out
* elsewhere! Also, allow allocations in ErrorContext within a critical
- * section. Otherwise a PANIC will cause an assertion failure in the
- * error reporting code, before printing out the real cause of the
- * failure.
+ * section. Otherwise a PANIC will cause an assertion failure in the error
+ * reporting code, before printing out the real cause of the failure.
*
* This should be the last step in this function, as elog.c assumes memory
* management works once ErrorContext is non-null.
@@ -150,6 +150,7 @@ MemoryContextReset(MemoryContext context)
/* Nothing to do if no pallocs since startup or last reset */
if (!context->isReset)
{
+ MemoryContextCallResetCallbacks(context);
(*context->methods->reset) (context);
context->isReset = true;
VALGRIND_DESTROY_MEMPOOL(context);
@@ -196,6 +197,14 @@ MemoryContextDelete(MemoryContext context)
MemoryContextDeleteChildren(context);
/*
+ * It's not entirely clear whether 'tis better to do this before or after
+ * delinking the context; but an error in a callback will likely result in
+ * leaking the whole context (if it's not a root context) if we do it
+ * after, so let's do it before.
+ */
+ MemoryContextCallResetCallbacks(context);
+
+ /*
* We delink the context from its parent before deleting it, so that if
* there's an error we won't have deleted/busted contexts still attached
* to the context tree. Better a leak than a crash.
@@ -243,6 +252,56 @@ MemoryContextResetAndDeleteChildren(MemoryContext context)
}
/*
+ * MemoryContextRegisterResetCallback
+ * Register a function to be called before next context reset/delete.
+ * Such callbacks will be called in reverse order of registration.
+ *
+ * The caller is responsible for allocating a MemoryContextCallback struct
+ * to hold the info about this callback request, and for filling in the
+ * "func" and "arg" fields in the struct to show what function to call with
+ * what argument. Typically the callback struct should be allocated within
+ * the specified context, since that means it will automatically be freed
+ * when no longer needed.
+ *
+ * There is no API for deregistering a callback once registered. If you
+ * want it to not do anything anymore, adjust the state pointed to by its
+ * "arg" to indicate that.
+ */
+void
+MemoryContextRegisterResetCallback(MemoryContext context,
+ MemoryContextCallback *cb)
+{
+ AssertArg(MemoryContextIsValid(context));
+
+ /* Push onto head so this will be called before older registrants. */
+ cb->next = context->reset_cbs;
+ context->reset_cbs = cb;
+ /* Mark the context as non-reset (it probably is already). */
+ context->isReset = false;
+}
+
+/*
+ * MemoryContextCallResetCallbacks
+ * Internal function to call all registered callbacks for context.
+ */
+static void
+MemoryContextCallResetCallbacks(MemoryContext context)
+{
+ MemoryContextCallback *cb;
+
+ /*
+ * We pop each callback from the list before calling. That way, if an
+ * error occurs inside the callback, we won't try to call it a second time
+ * in the likely event that we reset or delete the context later.
+ */
+ while ((cb = context->reset_cbs) != NULL)
+ {
+ context->reset_cbs = cb->next;
+ (*cb->func) (cb->arg);
+ }
+}
+
+/*
* MemoryContextSetParent
* Change a context to belong to a new parent (or no parent).
*
@@ -318,9 +377,8 @@ void
MemoryContextAllowInCriticalSection(MemoryContext context, bool allow)
{
AssertArg(MemoryContextIsValid(context));
-#ifdef USE_ASSERT_CHECKING
+
context->allowInCritSection = allow;
-#endif
}
/*
@@ -589,11 +647,8 @@ MemoryContextCreate(NodeTag tag, Size size,
node->parent = parent;
node->nextchild = parent->firstchild;
parent->firstchild = node;
-
-#ifdef USE_ASSERT_CHECKING
/* inherit allowInCritSection flag from parent */
node->allowInCritSection = parent->allowInCritSection;
-#endif
}
VALGRIND_CREATE_MEMPOOL(node, 0, false);
diff --git a/src/include/nodes/memnodes.h b/src/include/nodes/memnodes.h
index ca9c3dea3cb..3eeaad49288 100644
--- a/src/include/nodes/memnodes.h
+++ b/src/include/nodes/memnodes.h
@@ -17,6 +17,22 @@
#include "nodes/nodes.h"
/*
+ * A memory context can have callback functions registered on it. Any such
+ * function will be called once just before the context is next reset or
+ * deleted. The MemoryContextCallback struct describing such a callback
+ * typically would be allocated within the context itself, thereby avoiding
+ * any need to manage it explicitly (the reset/delete action will free it).
+ */
+typedef void (*MemoryContextCallbackFunction) (void *arg);
+
+typedef struct MemoryContextCallback
+{
+ MemoryContextCallbackFunction func; /* function to call */
+ void *arg; /* argument to pass it */
+ struct MemoryContextCallback *next; /* next in list of callbacks */
+} MemoryContextCallback;
+
+/*
* MemoryContext
* A logical context in which memory allocations occur.
*
@@ -54,15 +70,15 @@ typedef struct MemoryContextMethods
typedef struct MemoryContextData
{
NodeTag type; /* identifies exact kind of context */
+ /* these two fields are placed here to minimize alignment wastage: */
+ bool isReset; /* T = no space alloced since last reset */
+ bool allowInCritSection; /* allow palloc in critical section */
MemoryContextMethods *methods; /* virtual function table */
MemoryContext parent; /* NULL if no parent (toplevel context) */
MemoryContext firstchild; /* head of linked list of children */
MemoryContext nextchild; /* next child of same parent */
char *name; /* context name (just for debugging) */
- bool isReset; /* T = no space alloced since last reset */
-#ifdef USE_ASSERT_CHECKING
- bool allowInCritSection; /* allow palloc in critical section */
-#endif
+ MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */
} MemoryContextData;
/* utils/palloc.h contains typedef struct MemoryContextData *MemoryContext */
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 85aba7a7220..76ad7d443b0 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -94,6 +94,8 @@ extern void MemoryContextDelete(MemoryContext context);
extern void MemoryContextResetChildren(MemoryContext context);
extern void MemoryContextDeleteChildren(MemoryContext context);
extern void MemoryContextResetAndDeleteChildren(MemoryContext context);
+extern void MemoryContextRegisterResetCallback(MemoryContext context,
+ MemoryContextCallback *cb);
extern void MemoryContextSetParent(MemoryContext context,
MemoryContext new_parent);
extern Size GetMemoryChunkSpace(void *pointer);