diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 7ed35d5bdf123ed6b1725edf27d32e94c20aebb4..4eab24c46c7ab0f0b2a09770ac44ce2c6f4c5554 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -178,26 +178,8 @@ MemoryContextDelete(MemoryContext context) * there's an error we won't have deleted/busted contexts still attached * to the context tree. Better a leak than a crash. */ - if (context->parent) - { - MemoryContext parent = context->parent; - - if (context == parent->firstchild) - parent->firstchild = context->nextchild; - else - { - MemoryContext child; + MemoryContextSetParent(context, NULL); - for (child = parent->firstchild; child; child = child->nextchild) - { - if (context == child->nextchild) - { - child->nextchild = context->nextchild; - break; - } - } - } - } (*context->methods->delete_context) (context); pfree(context); } @@ -237,6 +219,67 @@ MemoryContextResetAndDeleteChildren(MemoryContext context) MemoryContextReset(context); } +/* + * MemoryContextSetParent + * Change a context to belong to a new parent (or no parent). + * + * We provide this as an API function because it is sometimes useful to + * change a context's lifespan after creation. For example, a context + * might be created underneath a transient context, filled with data, + * and then reparented underneath CacheMemoryContext to make it long-lived. + * In this way no special effort is needed to get rid of the context in case + * a failure occurs before its contents are completely set up. + * + * Callers often assume that this function cannot fail, so don't put any + * elog(ERROR) calls in it. + * + * A possible caller error is to reparent a context under itself, creating + * a loop in the context graph. We assert here that context != new_parent, + * but checking for multi-level loops seems more trouble than it's worth. + */ +void +MemoryContextSetParent(MemoryContext context, MemoryContext new_parent) +{ + AssertArg(MemoryContextIsValid(context)); + AssertArg(context != new_parent); + + /* Delink from existing parent, if any */ + if (context->parent) + { + MemoryContext parent = context->parent; + + if (context == parent->firstchild) + parent->firstchild = context->nextchild; + else + { + MemoryContext child; + + for (child = parent->firstchild; child; child = child->nextchild) + { + if (context == child->nextchild) + { + child->nextchild = context->nextchild; + break; + } + } + } + } + + /* And relink */ + if (new_parent) + { + AssertArg(MemoryContextIsValid(new_parent)); + context->parent = new_parent; + context->nextchild = new_parent->firstchild; + new_parent->firstchild = context; + } + else + { + context->parent = NULL; + context->nextchild = NULL; + } +} + /* * GetMemoryChunkSpace * Given a currently-allocated chunk, determine the total space @@ -489,6 +532,7 @@ MemoryContextCreate(NodeTag tag, Size size, (*node->methods->init) (node); /* OK to link node to parent (if any) */ + /* Could use MemoryContextSetParent here, but doesn't seem worthwhile */ if (parent) { node->parent = parent; diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 7c1202478e5a0ae38c71bcda7da8e5ab4e036a2d..94c78289b6e818c0a097f6492b6e19bd98c04430 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -90,6 +90,8 @@ extern void MemoryContextDelete(MemoryContext context); extern void MemoryContextResetChildren(MemoryContext context); extern void MemoryContextDeleteChildren(MemoryContext context); extern void MemoryContextResetAndDeleteChildren(MemoryContext context); +extern void MemoryContextSetParent(MemoryContext context, + MemoryContext new_parent); extern Size GetMemoryChunkSpace(void *pointer); extern MemoryContext GetMemoryChunkContext(void *pointer); extern bool MemoryContextIsEmpty(MemoryContext context);