Files
openGauss-server/contrib/gtm/common/mcxt.cpp
2020-06-30 17:38:27 +08:00

753 lines
22 KiB
C++

/* -------------------------------------------------------------------------
*
* mcxt.c
* POSTGRES memory context management code.
*
* This module handles context management operations that are independent
* of the particular kind of context being operated on. It calls
* context-type-specific operations via the function pointers in a
* context's MemoryContextMethods struct.
*
*
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2010-2012 Postgres-XC Development Group
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.65 2008/06/28 16:45:22 tgl Exp $
*
* -------------------------------------------------------------------------
*/
#include "gtm/gtm_c.h"
#include "gtm/memutils.h"
#include "gtm/elog.h"
#include "gtm/assert.h"
#include "gtm/gtm.h"
/*****************************************************************************
* GLOBAL MEMORY *
*****************************************************************************/
/*
* Standard top-level contexts. For a description of the purpose of each
* of these contexts, refer to src/backend/utils/mmgr/README
*/
static void MemoryContextStatsInternal(MemoryContext context, int level);
static void MemoryContextDeleteInternal(MemoryContext context, bool parent_locked);
void* allocTopMemCxt(size_t s);
MemoryContext TopMostMemoryContext;
/*****************************************************************************
* EXPORTED ROUTINES *
*****************************************************************************/
/*
* MemoryContextInit
* Start up the memory-context subsystem.
*
* This must be called before creating contexts or allocating memory in
* contexts. TopMemoryContext and ErrorContext are initialized here;
* other contexts must be created afterwards.
*
* In normal multi-backend operation, this is called once during
* postmaster startup, and not at all by individual backend startup
* (since the backends inherit an already-initialized context subsystem
* by virtue of being forked off the postmaster).
*
* In a standalone backend this must be called during backend startup.
*/
void MemoryContextInit(void)
{
AssertState(TopMemoryContext == NULL);
/*
* Initialize TopMemoryContext as an AllocSetContext with slow growth rate
* --- we don't really expect much to be allocated in it.
*
* (There is special-case code in MemoryContextCreate() for this call.)
*
* This context is shared between different threads and must be made
* thread-safe
*/
TopMemoryContext = AllocSetContextCreate((MemoryContext)NULL, "TopMemoryContext", 0, 8 * 1024, 8 * 1024, true);
TopMostMemoryContext = TopMemoryContext;
/*
* Not having any other place to point CurrentMemoryContext, make it point
* to TopMemoryContext. Caller should change this soon!
*/
CurrentMemoryContext = TopMemoryContext;
/*
* Initialize ErrorContext as an AllocSetContext with slow growth rate ---
* we don't really expect much to be allocated in it. More to the point,
* require it to contain at least 8K at all times. This is the only case
* 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!
*
* Similar to TopMostMemoryContext, this context may as well be shared
* between threads
*/
ErrorContext = AllocSetContextCreate(TopMemoryContext, "ErrorContext", 8 * 1024, 8 * 1024, 8 * 1024, true);
}
/*
* MemoryContextReset
* Release all space allocated within a context and its descendants,
* but don't delete the contexts themselves.
*
* The type-specific reset routine handles the context itself, but we
* have to do the recursion for the children.
*/
void MemoryContextReset(MemoryContext context)
{
AssertArg(MemoryContextIsValid(context));
if (MemoryContextIsShared(context)) {
MemoryContextLock(context);
}
/* save a function call in common case where there are no children */
if (context->firstchild != NULL) {
MemoryContextResetChildren(context);
}
if (MemoryContextIsShared(context)) {
MemoryContextUnlock(context);
}
(*context->methods->reset)(context);
}
/*
* MemoryContextResetChildren
* Release all space allocated within a context's descendants,
* but don't delete the contexts themselves. The named context
* itself is not touched.
*/
void MemoryContextResetChildren(MemoryContext context)
{
MemoryContext child;
AssertArg(MemoryContextIsValid(context));
/*
* For a shared context, lock the parent context before resetting the
* children contextes
*/
if (MemoryContextIsShared(context)) {
MemoryContextLock(context);
}
for (child = context->firstchild; child != NULL; child = child->nextchild) {
MemoryContextReset(child);
}
if (MemoryContextIsShared(context)) {
MemoryContextUnlock(context);
}
}
/*
* MemoryContextDelete
* Delete a context and its descendants, and release all space
* allocated therein.
*
* The type-specific delete routine removes all subsidiary storage
* for the context, but we have to delete the context node itself,
* as well as recurse to get the children. We must also delink the
* node from its parent, if it has one.
*/
static void MemoryContextDeleteInternal(MemoryContext context, bool parent_locked)
{
AssertArg(MemoryContextIsValid(context));
/* We had better not be deleting TopMemoryContext ... */
Assert(context != TopMemoryContext);
/* And not CurrentMemoryContext, either */
Assert(context != CurrentMemoryContext);
MemoryContextDeleteChildren(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.
*/
if (context->parent) {
MemoryContext parent = context->parent;
/*
* If the parent context is shared and is already locked by the caller,
* no need to relock again. In fact, that's not the right thing to do
* since it will lead to a self-deadlock
*/
if (MemoryContextIsShared(parent) && (!parent_locked)) {
MemoryContextLock(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;
}
}
}
if (MemoryContextIsShared(parent) && (!parent_locked)) {
MemoryContextUnlock(parent);
}
}
(*context->methods->delete)(context);
pfree(context);
}
void MemoryContextDelete(MemoryContext context)
{
MemoryContextDeleteInternal(context, false);
}
/*
* MemoryContextDeleteChildren
* Delete all the descendants of the named context and release all
* space allocated therein. The named context itself is not touched.
*/
void MemoryContextDeleteChildren(MemoryContext context)
{
AssertArg(MemoryContextIsValid(context));
if (MemoryContextIsShared(context)) {
MemoryContextLock(context);
}
/*
* MemoryContextDelete will delink the child from me, so just iterate as
* long as there is a child.
*
* Since the parent is already locked, pass that information to the child
* which would then not attempt to relock the parent
*/
while (context->firstchild != NULL)
MemoryContextDeleteInternal(context->firstchild, true);
if (MemoryContextIsShared(context)) {
MemoryContextUnlock(context);
}
}
/*
* MemoryContextResetAndDeleteChildren
* Release all space allocated within a context and delete all
* its descendants.
*
* This is a common combination case where we want to preserve the
* specific context but get rid of absolutely everything under it.
*/
void MemoryContextResetAndDeleteChildren(MemoryContext context)
{
AssertArg(MemoryContextIsValid(context));
MemoryContextDeleteChildren(context);
(*context->methods->reset)(context);
}
/*
* GetMemoryChunkSpace
* Given a currently-allocated chunk, determine the total space
* it occupies (including all memory-allocation overhead).
*
* This is useful for measuring the total space occupied by a set of
* allocated chunks.
*/
Size GetMemoryChunkSpace(void* pointer)
{
StandardChunkHeader* header;
/*
* Try to detect bogus pointers handed to us, poorly though we can.
* Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
* allocated chunk.
*/
Assert(pointer != NULL);
Assert(pointer == (void*)MAXALIGN(pointer));
/*
* OK, it's probably safe to look at the chunk header.
*/
header = (StandardChunkHeader*)((char*)pointer - STANDARDCHUNKHEADERSIZE);
AssertArg(MemoryContextIsValid(header->context));
return (*header->context->methods->get_chunk_space)(header->context, pointer);
}
/*
* GetMemoryChunkContext
* Given a currently-allocated chunk, determine the context
* it belongs to.
*/
MemoryContext GetMemoryChunkContext(void* pointer)
{
StandardChunkHeader* header;
/*
* Try to detect bogus pointers handed to us, poorly though we can.
* Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
* allocated chunk.
*/
Assert(pointer != NULL);
Assert(pointer == (void*)MAXALIGN(pointer));
/*
* OK, it's probably safe to look at the chunk header.
*/
header = (StandardChunkHeader*)((char*)pointer - STANDARDCHUNKHEADERSIZE);
AssertArg(MemoryContextIsValid(header->context));
return header->context;
}
/*
* 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.
*
* This is just a debugging utility, so it's not fancy. The statistics
* are merely sent to stderr.
*/
void MemoryContextStats(MemoryContext context)
{
MemoryContextStatsInternal(context, 0);
}
static void MemoryContextStatsInternal(MemoryContext context, int level)
{
MemoryContext child;
AssertArg(MemoryContextIsValid(context));
(*context->methods->stats)(context, level);
for (child = context->firstchild; child != NULL; child = child->nextchild) {
MemoryContextStatsInternal(child, level + 1);
}
}
/*
* MemoryContextCheck
* Check all chunks in the named context.
*
* This is just a debugging utility, so it's not fancy.
*/
#ifdef MEMORY_CONTEXT_CHECKING
void MemoryContextCheck(MemoryContext context)
{
MemoryContext child;
AssertArg(MemoryContextIsValid(context));
(*context->methods->check)(context);
for (child = context->firstchild; child != NULL; child = child->nextchild) {
MemoryContextCheck(child);
}
}
#endif
/*
* MemoryContextContains
* Detect whether an allocated chunk of memory belongs to a given
* context or not.
*
* Caution: this test is reliable as long as 'pointer' does point to
* a chunk of memory allocated from *some* context. If 'pointer' points
* at memory obtained in some other way, there is a small chance of a
* false-positive result, since the bits right before it might look like
* a valid chunk header by chance.
*/
bool MemoryContextContains(MemoryContext context, void* pointer)
{
StandardChunkHeader* header;
/*
* Try to detect bogus pointers handed to us, poorly though we can.
* Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
* allocated chunk.
*/
if (pointer == NULL || pointer != (void*)MAXALIGN(pointer)) {
return false;
}
/*
* OK, it's probably safe to look at the chunk header.
*/
header = (StandardChunkHeader*)((char*)pointer - STANDARDCHUNKHEADERSIZE);
/*
* If the context link doesn't match then we certainly have a non-member
* chunk. Also check for a reasonable-looking size as extra guard against
* being fooled by bogus pointers.
*/
if (header->context == context && AllocSizeIsValid(header->size)) {
return true;
}
return false;
}
/* --------------------
* MemoryContextCreate
* Context-type-independent part of context creation.
*
* This is only intended to be called by context-type-specific
* context creation routines, not by the unwashed masses.
*
* The context creation procedure is a little bit tricky because
* we want to be sure that we don't leave the context tree invalid
* in case of failure (such as insufficient memory to allocate the
* context node itself). The procedure goes like this:
* 1. Context-type-specific routine first calls MemoryContextCreate(),
* passing the appropriate tag/size/methods values (the methods
* pointer will ordinarily point to statically allocated data).
* The parent and name parameters usually come from the caller.
* 2. MemoryContextCreate() attempts to allocate the context node,
* plus space for the name. If this fails we can ereport() with no
* damage done.
* 3. We fill in all of the type-independent MemoryContext fields.
* 4. We call the type-specific init routine (using the methods pointer).
* The init routine is required to make the node minimally valid
* with zero chance of failure --- it can't allocate more memory,
* for example.
* 5. Now we have a minimally valid node that can behave correctly
* when told to reset or delete itself. We link the node to its
* parent (if any), making the node part of the context tree.
* 6. We return to the context-type-specific routine, which finishes
* up type-specific initialization. This routine can now do things
* that might fail (like allocate more memory), so long as it's
* sure the node is left in a state that delete will handle.
*
* This protocol doesn't prevent us from leaking memory if step 6 fails
* during creation of a top-level context, since there's no parent link
* in that case. However, if you run out of memory while you're building
* a top-level context, you might as well go home anyway...
*
* Normally, the context node and the name are allocated from
* TopMemoryContext (NOT from the parent context, since the node must
* survive resets of its parent context!). However, this routine is itself
* used to create TopMemoryContext! If we see that TopMemoryContext is NULL,
* we assume we are creating TopMemoryContext and use malloc() to allocate
* the node.
*
* Note that the name field of a MemoryContext does not point to
* separately-allocated storage, so it should not be freed at context
* deletion.
* --------------------
*/
MemoryContext MemoryContextCreate(Size size, MemoryContextMethods* methods, MemoryContext parent, const char* name)
{
MemoryContext node;
Size nameLen = strlen(name);
Size needed = size + nameLen + 1;
errno_t rc;
/* Get space for node and name */
if (TopMemoryContext != NULL) {
/* Normal case: allocate the node in TopMemoryContext */
node = (MemoryContext)MemoryContextAlloc(TopMemoryContext, needed);
} else {
/* Special case for startup: use good ol' malloc */
node = (MemoryContext)malloc(needed);
Assert(node != NULL);
}
/* Initialize the node as best we can */
rc = memset_s(node, size, 0, size);
securec_check(rc, "\0", "\0");
node->methods = methods;
node->parent = NULL; /* for the moment */
node->firstchild = NULL;
node->nextchild = NULL;
node->name = ((char*)node) + size;
rc = strcpy_s(node->name, nameLen + 1, name);
securec_check(rc, "\0", "\0");
/* Type-specific routine finishes any other essential initialization */
(*node->methods->init)(node);
/*
* Lock the parent context if the it is shared and must be made thread-safe
*/
if ((parent != NULL) && (MemoryContextIsShared(parent))) {
MemoryContextLock(parent);
}
/* OK to link node to parent (if any) */
if (parent) {
node->parent = parent;
node->nextchild = parent->firstchild;
parent->firstchild = node;
}
if ((parent != NULL) && (MemoryContextIsShared(parent))) {
MemoryContextUnlock(parent);
}
/* Return to type-specific creation routine to finish up */
return node;
}
/*
* MemoryContextAlloc
* Allocate space within the specified context.
*
* This could be turned into a macro, but we'd have to import
* nodes/memnodes.h into postgres.h which seems a bad idea.
*/
void* MemoryContextAlloc(MemoryContext context, Size size)
{
AssertArg(MemoryContextIsValid(context));
if (!AllocSizeIsValid(size)) {
elog(ERROR, "invalid memory alloc request size %lu", (unsigned long)size);
}
return (*context->methods->alloc)(context, size);
}
/*
* MemoryContextAllocZero
* Like MemoryContextAlloc, but clears allocated memory
*
* We could just call MemoryContextAlloc then clear the memory, but this
* is a very common combination, so we provide the combined operation.
*/
void* MemoryContextAllocZero(MemoryContext context, Size size)
{
void* ret;
AssertArg(MemoryContextIsValid(context));
if (!AllocSizeIsValid(size)) {
elog(ERROR, "invalid memory alloc request size %lu", (unsigned long)size);
}
ret = (*context->methods->alloc)(context, size);
MemSetAligned(ret, 0, size);
return ret;
}
/*
* MemoryContextAllocZeroAligned
* MemoryContextAllocZero where length is suitable for MemSetLoop
*
* This might seem overly specialized, but it's not because newNode()
* is so often called with compile-time-constant sizes.
*/
void* MemoryContextAllocZeroAligned(MemoryContext context, Size size)
{
void* ret;
AssertArg(MemoryContextIsValid(context));
if (!AllocSizeIsValid(size)) {
elog(ERROR, "invalid memory alloc request size %lu", (unsigned long)size);
}
ret = (*context->methods->alloc)(context, size);
MemSetLoop(ret, 0, size);
return ret;
}
/*
* pfree
* Release an allocated chunk.
*/
void pfree(void* pointer)
{
StandardChunkHeader* header;
/*
* Try to detect bogus pointers handed to us, poorly though we can.
* Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
* allocated chunk.
*/
Assert(pointer != NULL);
Assert(pointer == (void*)MAXALIGN(pointer));
/*
* OK, it's probably safe to look at the chunk header.
*/
header = (StandardChunkHeader*)((char*)pointer - STANDARDCHUNKHEADERSIZE);
AssertArg(MemoryContextIsValid(header->context));
(*header->context->methods->free_p)(header->context, pointer);
}
/*
* repalloc
* Adjust the size of a previously allocated chunk.
*/
void* repalloc(void* pointer, Size size)
{
StandardChunkHeader* header;
/*
* Try to detect bogus pointers handed to us, poorly though we can.
* Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
* allocated chunk.
*/
Assert(pointer != NULL);
Assert(pointer == (void*)MAXALIGN(pointer));
/*
* OK, it's probably safe to look at the chunk header.
*/
header = (StandardChunkHeader*)((char*)pointer - STANDARDCHUNKHEADERSIZE);
AssertArg(MemoryContextIsValid(header->context));
if (!AllocSizeIsValid(size)) {
elog(ERROR, "invalid memory alloc request size %lu", (unsigned long)size);
}
return (*header->context->methods->realloc)(header->context, pointer, size);
}
/*
* MemoryContextSwitchTo
* Returns the current context; installs the given context.
*
* This is inlined when using GCC.
*
* TODO: investigate supporting inlining for some non-GCC compilers.
*/
MemoryContext MemoryContextSwitchTo(MemoryContext context)
{
MemoryContext old;
AssertArg(MemoryContextIsValid(context));
old = CurrentMemoryContext;
CurrentMemoryContext = context;
return old;
}
/*
* MemoryContextStrdup
* Like strdup(), but allocate from the specified context
*/
char* MemoryContextStrdup(MemoryContext context, const char* string)
{
char* nstr;
Size len = strlen(string) + 1;
errno_t rc;
nstr = (char*)MemoryContextAlloc(context, len);
rc = memcpy_s(nstr, len, string, len);
securec_check(rc, "\0", "\0");
return nstr;
}
/*
* pnstrdup
* Like pstrdup(), but append null byte to a
* not-necessarily-null-terminated input string.
*/
char* pnstrdup(const char* in, Size len)
{
char* out = palloc(len + 1);
errno_t rc;
rc = memcpy_s(out, len, in, len);
securec_check(rc, "\0", "\0");
out[len] = '\0';
return out;
}
#if defined(WIN32) || defined(__CYGWIN__)
/*
* Memory support routines for libpgport on Win32
*
* Win32 can't load a library that PGDLLIMPORTs a variable
* if the link object files also PGDLLIMPORT the same variable.
* For this reason, libpgport can't reference CurrentMemoryContext
* in the palloc macro calls.
*
* To fix this, we create several functions here that allow us to
* manage memory without doing the inline in libpgport.
*/
void* pgport_palloc(Size sz)
{
return palloc(sz);
}
char* pgport_pstrdup(const char* str)
{
return pstrdup(str);
}
/* Doesn't reference a PGDLLIMPORT variable, but here for completeness. */
void pgport_pfree(void* pointer)
{
pfree(pointer);
}
#endif
#include "gen_alloc.h"
void* current_memcontext(void);
void* current_memcontext(void)
{
return ((void*)CurrentMemoryContext);
}
void* allocTopMemCxt(size_t s)
{
return (void*)MemoryContextAlloc(TopMostMemoryContext, (Size)s);
}
Gen_Alloc genAlloc_class = {(void*)MemoryContextAlloc,
(void*)MemoryContextAllocZero,
(void*)repalloc,
(void*)pfree,
(void*)current_memcontext,
(void*)allocTopMemCxt};