#include "postgres.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"
#include "utils/memutils_internal.h"
#include "utils/memutils_memorychunk.h"
static void BogusFree(void *pointer);
static void *BogusRealloc(void *pointer, Size size, int flags);
static MemoryContext BogusGetChunkContext(void *pointer);
static Size BogusGetChunkSpace(void *pointer);
#define BOGUS_MCTX(id) \
[id].free_p = BogusFree, \
[id].realloc = BogusRealloc, \
[id].get_chunk_context = BogusGetChunkContext, \
[id].get_chunk_space = BogusGetChunkSpace
static const MemoryContextMethods mcxt_methods[] = {
[MCTX_ASET_ID].alloc = AllocSetAlloc,
[MCTX_ASET_ID].free_p = AllocSetFree,
[MCTX_ASET_ID].realloc = AllocSetRealloc,
[MCTX_ASET_ID].reset = AllocSetReset,
[MCTX_ASET_ID].delete_context = AllocSetDelete,
[MCTX_ASET_ID].get_chunk_context = AllocSetGetChunkContext,
[MCTX_ASET_ID].get_chunk_space = AllocSetGetChunkSpace,
[MCTX_ASET_ID].is_empty = AllocSetIsEmpty,
[MCTX_ASET_ID].stats = AllocSetStats,
#ifdef MEMORY_CONTEXT_CHECKING
[MCTX_ASET_ID].check = AllocSetCheck,
#endif
[MCTX_GENERATION_ID].alloc = GenerationAlloc,
[MCTX_GENERATION_ID].free_p = GenerationFree,
[MCTX_GENERATION_ID].realloc = GenerationRealloc,
[MCTX_GENERATION_ID].reset = GenerationReset,
[MCTX_GENERATION_ID].delete_context = GenerationDelete,
[MCTX_GENERATION_ID].get_chunk_context = GenerationGetChunkContext,
[MCTX_GENERATION_ID].get_chunk_space = GenerationGetChunkSpace,
[MCTX_GENERATION_ID].is_empty = GenerationIsEmpty,
[MCTX_GENERATION_ID].stats = GenerationStats,
#ifdef MEMORY_CONTEXT_CHECKING
[MCTX_GENERATION_ID].check = GenerationCheck,
#endif
[MCTX_SLAB_ID].alloc = SlabAlloc,
[MCTX_SLAB_ID].free_p = SlabFree,
[MCTX_SLAB_ID].realloc = SlabRealloc,
[MCTX_SLAB_ID].reset = SlabReset,
[MCTX_SLAB_ID].delete_context = SlabDelete,
[MCTX_SLAB_ID].get_chunk_context = SlabGetChunkContext,
[MCTX_SLAB_ID].get_chunk_space = SlabGetChunkSpace,
[MCTX_SLAB_ID].is_empty = SlabIsEmpty,
[MCTX_SLAB_ID].stats = SlabStats,
#ifdef MEMORY_CONTEXT_CHECKING
[MCTX_SLAB_ID].check = SlabCheck,
#endif
[MCTX_ALIGNED_REDIRECT_ID].alloc = NULL,
[MCTX_ALIGNED_REDIRECT_ID].free_p = AlignedAllocFree,
[MCTX_ALIGNED_REDIRECT_ID].realloc = AlignedAllocRealloc,
[MCTX_ALIGNED_REDIRECT_ID].reset = NULL,
[MCTX_ALIGNED_REDIRECT_ID].delete_context = NULL,
[MCTX_ALIGNED_REDIRECT_ID].get_chunk_context = AlignedAllocGetChunkContext,
[MCTX_ALIGNED_REDIRECT_ID].get_chunk_space = AlignedAllocGetChunkSpace,
[MCTX_ALIGNED_REDIRECT_ID].is_empty = NULL,
[MCTX_ALIGNED_REDIRECT_ID].stats = NULL,
#ifdef MEMORY_CONTEXT_CHECKING
[MCTX_ALIGNED_REDIRECT_ID].check = NULL,
#endif
[MCTX_BUMP_ID].alloc = BumpAlloc,
[MCTX_BUMP_ID].free_p = BumpFree,
[MCTX_BUMP_ID].realloc = BumpRealloc,
[MCTX_BUMP_ID].reset = BumpReset,
[MCTX_BUMP_ID].delete_context = BumpDelete,
[MCTX_BUMP_ID].get_chunk_context = BumpGetChunkContext,
[MCTX_BUMP_ID].get_chunk_space = BumpGetChunkSpace,
[MCTX_BUMP_ID].is_empty = BumpIsEmpty,
[MCTX_BUMP_ID].stats = BumpStats,
#ifdef MEMORY_CONTEXT_CHECKING
[MCTX_BUMP_ID].check = BumpCheck,
#endif
BOGUS_MCTX(MCTX_1_RESERVED_GLIBC_ID),
BOGUS_MCTX(MCTX_2_RESERVED_GLIBC_ID),
BOGUS_MCTX(MCTX_8_UNUSED_ID),
BOGUS_MCTX(MCTX_9_UNUSED_ID),
BOGUS_MCTX(MCTX_10_UNUSED_ID),
BOGUS_MCTX(MCTX_11_UNUSED_ID),
BOGUS_MCTX(MCTX_12_UNUSED_ID),
BOGUS_MCTX(MCTX_13_UNUSED_ID),
BOGUS_MCTX(MCTX_14_UNUSED_ID),
BOGUS_MCTX(MCTX_0_RESERVED_UNUSEDMEM_ID),
BOGUS_MCTX(MCTX_15_RESERVED_WIPEDMEM_ID)
};
#undef BOGUS_MCTX
__thread MemoryContext CurrentMemoryContext = NULL;
__thread MemoryContext TopMemoryContext = NULL;
__thread MemoryContext ErrorContext = NULL;
static void MemoryContextDeleteOnly(MemoryContext context);
static void MemoryContextCallResetCallbacks(MemoryContext context);
static void MemoryContextStatsInternal(MemoryContext context, int level,
int max_level, int max_children,
MemoryContextCounters *totals,
bool print_to_stderr);
static void MemoryContextStatsPrint(MemoryContext context, void *passthru,
const char *stats_string,
bool print_to_stderr);
#define AssertNotInCriticalSection(context) \
Assert(CritSectionCount == 0 || (context)->allowInCritSection)
#define MCXT_METHOD(pointer, method) \
mcxt_methods[GetMemoryChunkMethodID(pointer)].method
static inline MemoryContextMethodID
GetMemoryChunkMethodID(const void *pointer)
{
uint64 header;
Assert(pointer == (const void *) MAXALIGN(pointer));
VALGRIND_MAKE_MEM_DEFINED((char *) pointer - sizeof(uint64), sizeof(uint64));
header = *((const uint64 *) ((const char *) pointer - sizeof(uint64)));
VALGRIND_MAKE_MEM_NOACCESS((char *) pointer - sizeof(uint64), sizeof(uint64));
return (MemoryContextMethodID) (header & MEMORY_CONTEXT_METHODID_MASK);
}
static inline uint64
GetMemoryChunkHeader(const void *pointer)
{
uint64 header;
VALGRIND_MAKE_MEM_DEFINED((char *) pointer - sizeof(uint64), sizeof(uint64));
header = *((const uint64 *) ((const char *) pointer - sizeof(uint64)));
VALGRIND_MAKE_MEM_NOACCESS((char *) pointer - sizeof(uint64), sizeof(uint64));
return header;
}
static MemoryContext
MemoryContextTraverseNext(MemoryContext curr, MemoryContext top)
{
if (curr->firstchild != NULL)
return curr->firstchild;
while (curr->nextchild == NULL)
{
curr = curr->parent;
if (curr == top)
return NULL;
}
return curr->nextchild;
}
static void
BogusFree(void *pointer)
{
elog(ERROR, "pfree called with invalid pointer %p (header 0x%016llx)",
pointer, (unsigned long long) GetMemoryChunkHeader(pointer));
}
static void *
BogusRealloc(void *pointer, Size size, int flags)
{
elog(ERROR, "repalloc called with invalid pointer %p (header 0x%016llx)",
pointer, (unsigned long long) GetMemoryChunkHeader(pointer));
return NULL;
}
static MemoryContext
BogusGetChunkContext(void *pointer)
{
elog(ERROR, "GetMemoryChunkContext called with invalid pointer %p (header 0x%016llx)",
pointer, (unsigned long long) GetMemoryChunkHeader(pointer));
return NULL;
}
static Size
BogusGetChunkSpace(void *pointer)
{
elog(ERROR, "GetMemoryChunkSpace called with invalid pointer %p (header 0x%016llx)",
pointer, (unsigned long long) GetMemoryChunkHeader(pointer));
return 0;
}
void
MemoryContextInit(void)
{
Assert(TopMemoryContext == NULL);
TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL,
"TopMemoryContext",
ALLOCSET_DEFAULT_SIZES);
CurrentMemoryContext = TopMemoryContext;
ErrorContext = AllocSetContextCreate(TopMemoryContext,
"ErrorContext",
8 * 1024,
8 * 1024,
8 * 1024);
MemoryContextAllowInCriticalSection(ErrorContext, true);
}
void
MemoryContextReset(MemoryContext context)
{
Assert(MemoryContextIsValid(context));
if (context->firstchild != NULL)
MemoryContextDeleteChildren(context);
if (!context->isReset)
MemoryContextResetOnly(context);
}
void
MemoryContextResetOnly(MemoryContext context)
{
Assert(MemoryContextIsValid(context));
if (!context->isReset)
{
MemoryContextCallResetCallbacks(context);
context->methods->reset(context);
context->isReset = true;
VALGRIND_DESTROY_MEMPOOL(context);
VALGRIND_CREATE_MEMPOOL(context, 0, false);
}
}
void
MemoryContextDelete(MemoryContext context)
{
MemoryContext curr;
Assert(MemoryContextIsValid(context));
curr = context;
for (;;)
{
MemoryContext parent;
while (curr->firstchild != NULL)
curr = curr->firstchild;
parent = curr->parent;
MemoryContextDeleteOnly(curr);
if (curr == context)
break;
curr = parent;
}
}
static void
MemoryContextDeleteOnly(MemoryContext context)
{
Assert(MemoryContextIsValid(context));
Assert(context != TopMemoryContext);
Assert(context != CurrentMemoryContext);
Assert(context->firstchild == NULL);
MemoryContextCallResetCallbacks(context);
MemoryContextSetParent(context, NULL);
context->ident = NULL;
context->methods->delete_context(context);
VALGRIND_DESTROY_MEMPOOL(context);
}
void
MemoryContextDeleteChildren(MemoryContext context)
{
Assert(MemoryContextIsValid(context));
while (context->firstchild != NULL)
MemoryContextDelete(context->firstchild);
}
static void
MemoryContextCallResetCallbacks(MemoryContext context)
{
MemoryContextCallback *cb;
while ((cb = context->reset_cbs) != NULL)
{
context->reset_cbs = cb->next;
cb->func(cb->arg);
}
}
void
MemoryContextSetParent(MemoryContext context, MemoryContext new_parent)
{
Assert(MemoryContextIsValid(context));
Assert(context != new_parent);
if (new_parent == context->parent)
return;
if (context->parent)
{
MemoryContext parent = context->parent;
if (context->prevchild != NULL)
context->prevchild->nextchild = context->nextchild;
else
{
Assert(parent->firstchild == context);
parent->firstchild = context->nextchild;
}
if (context->nextchild != NULL)
context->nextchild->prevchild = context->prevchild;
}
if (new_parent)
{
Assert(MemoryContextIsValid(new_parent));
context->parent = new_parent;
context->prevchild = NULL;
context->nextchild = new_parent->firstchild;
if (new_parent->firstchild != NULL)
new_parent->firstchild->prevchild = context;
new_parent->firstchild = context;
}
else
{
context->parent = NULL;
context->prevchild = NULL;
context->nextchild = NULL;
}
}
void
MemoryContextAllowInCriticalSection(MemoryContext context, bool allow)
{
Assert(MemoryContextIsValid(context));
context->allowInCritSection = allow;
}
MemoryContext
GetMemoryChunkContext(void *pointer)
{
return MCXT_METHOD(pointer, get_chunk_context) (pointer);
}
Size
GetMemoryChunkSpace(void *pointer)
{
return MCXT_METHOD(pointer, get_chunk_space) (pointer);
}
void
MemoryContextStats(MemoryContext context)
{
MemoryContextStatsDetail(context, 100, 100, true);
}
void
MemoryContextStatsDetail(MemoryContext context,
int max_level, int max_children,
bool print_to_stderr)
{
MemoryContextCounters grand_totals;
memset(&grand_totals, 0, sizeof(grand_totals));
MemoryContextStatsInternal(context, 0, max_level, max_children,
&grand_totals, print_to_stderr);
if (print_to_stderr)
fprintf(stderr,
"Grand total: %zu bytes in %zu blocks; %zu free (%zu chunks); %zu used\n",
grand_totals.totalspace, grand_totals.nblocks,
grand_totals.freespace, grand_totals.freechunks,
grand_totals.totalspace - grand_totals.freespace);
else
{
ereport(LOG_SERVER_ONLY,
(errhidestmt(true),
errhidecontext(true),
errmsg_internal("Grand total: %zu bytes in %zu blocks; %zu free (%zu chunks); %zu used",
grand_totals.totalspace, grand_totals.nblocks,
grand_totals.freespace, grand_totals.freechunks,
grand_totals.totalspace - grand_totals.freespace)));
}
}
static void
MemoryContextStatsInternal(MemoryContext context, int level,
int max_level, int max_children,
MemoryContextCounters *totals,
bool print_to_stderr)
{
MemoryContext child;
int ichild;
Assert(MemoryContextIsValid(context));
context->methods->stats(context,
MemoryContextStatsPrint,
(void *) &level,
totals, print_to_stderr);
child = context->firstchild;
ichild = 0;
if (level < max_level && !stack_is_too_deep())
{
for (; child != NULL && ichild < max_children;
child = child->nextchild, ichild++)
{
MemoryContextStatsInternal(child, level + 1,
max_level, max_children,
totals,
print_to_stderr);
}
}
if (child != NULL)
{
MemoryContextCounters local_totals;
memset(&local_totals, 0, sizeof(local_totals));
ichild = 0;
while (child != NULL)
{
child->methods->stats(child, NULL, NULL, &local_totals, false);
ichild++;
child = MemoryContextTraverseNext(child, context);
}
if (print_to_stderr)
{
for (int i = 0; i <= level; i++)
fprintf(stderr, " ");
fprintf(stderr,
"%d more child contexts containing %zu total in %zu blocks; %zu free (%zu chunks); %zu used\n",
ichild,
local_totals.totalspace,
local_totals.nblocks,
local_totals.freespace,
local_totals.freechunks,
local_totals.totalspace - local_totals.freespace);
}
else
ereport(LOG_SERVER_ONLY,
(errhidestmt(true),
errhidecontext(true),
errmsg_internal("level: %d; %d more child contexts containing %zu total in %zu blocks; %zu free (%zu chunks); %zu used",
level,
ichild,
local_totals.totalspace,
local_totals.nblocks,
local_totals.freespace,
local_totals.freechunks,
local_totals.totalspace - local_totals.freespace)));
if (totals)
{
totals->nblocks += local_totals.nblocks;
totals->freechunks += local_totals.freechunks;
totals->totalspace += local_totals.totalspace;
totals->freespace += local_totals.freespace;
}
}
}
static void
MemoryContextStatsPrint(MemoryContext context, void *passthru,
const char *stats_string,
bool print_to_stderr)
{
int level = *(int *) passthru;
const char *name = context->name;
const char *ident = context->ident;
char truncated_ident[110];
int i;
if (ident && strcmp(name, "dynahash") == 0)
{
name = ident;
ident = NULL;
}
truncated_ident[0] = '\0';
if (ident)
{
int idlen = strlen(ident);
bool truncated = false;
strcpy(truncated_ident, ": ");
i = strlen(truncated_ident);
if (idlen > 100)
{
idlen = pg_mbcliplen(ident, idlen, 100);
truncated = true;
}
while (idlen-- > 0)
{
unsigned char c = *ident++;
if (c < ' ')
c = ' ';
truncated_ident[i++] = c;
}
truncated_ident[i] = '\0';
if (truncated)
strcat(truncated_ident, "...");
}
if (print_to_stderr)
{
for (i = 0; i < level; i++)
fprintf(stderr, " ");
fprintf(stderr, "%s: %s%s\n", name, stats_string, truncated_ident);
}
else
ereport(LOG_SERVER_ONLY,
(errhidestmt(true),
errhidecontext(true),
errmsg_internal("level: %d; %s: %s%s",
level, name, stats_string, truncated_ident)));
}
#ifdef MEMORY_CONTEXT_CHECKING
#endif
void
MemoryContextCreate(MemoryContext node,
NodeTag tag,
MemoryContextMethodID method_id,
MemoryContext parent,
const char *name)
{
Assert(CritSectionCount == 0);
node->type = tag;
node->isReset = true;
node->methods = &mcxt_methods[method_id];
node->parent = parent;
node->firstchild = NULL;
node->mem_allocated = 0;
node->prevchild = NULL;
node->name = name;
node->ident = NULL;
node->reset_cbs = NULL;
if (parent)
{
node->nextchild = parent->firstchild;
if (parent->firstchild != NULL)
parent->firstchild->prevchild = node;
parent->firstchild = node;
node->allowInCritSection = parent->allowInCritSection;
}
else
{
node->nextchild = NULL;
node->allowInCritSection = false;
}
VALGRIND_CREATE_MEMPOOL(node, 0, false);
}
void *
MemoryContextAllocationFailure(MemoryContext context, Size size, int flags)
{
if ((flags & MCXT_ALLOC_NO_OOM) == 0)
{
MemoryContextStats(TopMemoryContext);
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory"),
errdetail("Failed on request of size %zu in memory context \"%s\".",
size, context->name)));
}
return NULL;
}
void
MemoryContextSizeFailure(MemoryContext context, Size size, int flags)
{
elog(ERROR, "invalid memory alloc request size %zu", size);
}
void *
MemoryContextAlloc(MemoryContext context, Size size)
{
void *ret;
Assert(MemoryContextIsValid(context));
AssertNotInCriticalSection(context);
context->isReset = false;
ret = context->methods->alloc(context, size, 0);
VALGRIND_MEMPOOL_ALLOC(context, ret, size);
return ret;
}
void *
MemoryContextAllocZero(MemoryContext context, Size size)
{
void *ret;
Assert(MemoryContextIsValid(context));
AssertNotInCriticalSection(context);
context->isReset = false;
ret = context->methods->alloc(context, size, 0);
VALGRIND_MEMPOOL_ALLOC(context, ret, size);
MemSetAligned(ret, 0, size);
return ret;
}
void *
MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
{
void *ret;
Assert(MemoryContextIsValid(context));
AssertNotInCriticalSection(context);
if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) :
AllocSizeIsValid(size)))
elog(ERROR, "invalid memory alloc request size %zu", size);
context->isReset = false;
ret = context->methods->alloc(context, size, flags);
if (unlikely(ret == NULL))
return NULL;
VALGRIND_MEMPOOL_ALLOC(context, ret, size);
if ((flags & MCXT_ALLOC_ZERO) != 0)
MemSetAligned(ret, 0, size);
return ret;
}
void *
palloc(Size size)
{
void *ret;
MemoryContext context = CurrentMemoryContext;
Assert(MemoryContextIsValid(context));
AssertNotInCriticalSection(context);
context->isReset = false;
ret = context->methods->alloc(context, size, 0);
Assert(ret != NULL);
VALGRIND_MEMPOOL_ALLOC(context, ret, size);
return ret;
}
void *
palloc0(Size size)
{
void *ret;
MemoryContext context = CurrentMemoryContext;
Assert(MemoryContextIsValid(context));
AssertNotInCriticalSection(context);
context->isReset = false;
ret = context->methods->alloc(context, size, 0);
VALGRIND_MEMPOOL_ALLOC(context, ret, size);
MemSetAligned(ret, 0, size);
return ret;
}
void *
MemoryContextAllocAligned(MemoryContext context,
Size size, Size alignto, int flags)
{
MemoryChunk *alignedchunk;
Size alloc_size;
void *unaligned;
void *aligned;
Assert(alignto < (128 * 1024 * 1024));
Assert((alignto & (alignto - 1)) == 0);
if (unlikely(alignto <= MAXIMUM_ALIGNOF))
return MemoryContextAllocExtended(context, size, flags);
alloc_size = size + PallocAlignedExtraBytes(alignto);
#ifdef MEMORY_CONTEXT_CHECKING
alloc_size += 1;
#endif
unaligned = MemoryContextAllocExtended(context, alloc_size, flags);
aligned = (void *) TYPEALIGN(alignto, (char *) unaligned +
sizeof(MemoryChunk));
alignedchunk = PointerGetMemoryChunk(aligned);
MemoryChunkSetHdrMask(alignedchunk, unaligned, alignto,
MCTX_ALIGNED_REDIRECT_ID);
Assert((void *) TYPEALIGN(alignto, aligned) == aligned);
#ifdef MEMORY_CONTEXT_CHECKING
alignedchunk->requested_size = size;
set_sentinel(aligned, size);
#endif
VALGRIND_MAKE_MEM_NOACCESS(unaligned,
(char *) alignedchunk - (char *) unaligned);
VALGRIND_MAKE_MEM_NOACCESS(alignedchunk, sizeof(MemoryChunk));
return aligned;
}
void
pfree(void *pointer)
{
#ifdef USE_VALGRIND
MemoryContextMethodID method = GetMemoryChunkMethodID(pointer);
MemoryContext context = GetMemoryChunkContext(pointer);
#endif
MCXT_METHOD(pointer, free_p) (pointer);
#ifdef USE_VALGRIND
if (method != MCTX_ALIGNED_REDIRECT_ID)
VALGRIND_MEMPOOL_FREE(context, pointer);
#endif
}
void *
repalloc(void *pointer, Size size)
{
#ifdef USE_VALGRIND
MemoryContextMethodID method = GetMemoryChunkMethodID(pointer);
#endif
#if defined(USE_ASSERT_CHECKING) || defined(USE_VALGRIND)
MemoryContext context = GetMemoryChunkContext(pointer);
#endif
void *ret;
AssertNotInCriticalSection(context);
Assert(!context->isReset);
ret = MCXT_METHOD(pointer, realloc) (pointer, size, 0);
#ifdef USE_VALGRIND
if (method != MCTX_ALIGNED_REDIRECT_ID)
VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
#endif
return ret;
}
#if defined(USE_ASSERT_CHECKING) || defined(USE_VALGRIND)
#endif
char *
MemoryContextStrdup(MemoryContext context, const char *string)
{
char *nstr;
Size len = strlen(string) + 1;
nstr = (char *) MemoryContextAlloc(context, len);
memcpy(nstr, string, len);
return nstr;
}
char *
pstrdup(const char *in)
{
return MemoryContextStrdup(CurrentMemoryContext, in);
}