pgpool2.git / summary / log / commit / refs

commit    e4c7c63f3ac244ccd4d7163fe7816f2ddf52bcb7
Author:   Muhammad Usama <m.usama@gmail.com>
Date:     Thu Jun 29 15:30:59 2017 +0000

    Importing the latest changes in the MemoryManager API from PostgreSQL code.
    
    Pgpool-II has borrowed the MemoryManager from PostgreSQL source and recently
    there have been some performance updates made to the PostgreSQL's
    memory manager, from which Pgpool-II can also benefit from.
    This need was identified by the reporter of issue: "315: High CPU usage when
    commiting large transactions and using in (shared) memory cache"


src/include/parser/pg_config_manual.h | 9 + src/include/pool_type.h | 16 + src/include/utils/memnodes.h | 36 +- src/include/utils/memutils.h | 105 ++++-- src/include/utils/palloc.h | 91 +++-- src/utils/mmgr/aset.c | 482 ++++++++++++-------------- src/utils/mmgr/mcxt.c | 612 ++++++++++++++++++++++++---------- 7 files changed, 823 insertions(+), 528 deletions(-) diff --git a/src/include/parser/pg_config_manual.h b/src/include/parser/pg_config_manual.h index a7c83fe04..d7e153b2f 100644 --- a/src/include/parser/pg_config_manual.h +++ b/src/include/parser/pg_config_manual.h @@ -31,6 +31,15 @@ #define PG_PRINTF_ATTRIBUTE printf #endif +/* from postgresql/src/include/c.h */ +/* GCC and XLC support format attributes */ +#if defined(__GNUC__) || defined(__IBMC__) +#define pg_attribute_format_arg(a) __attribute__((format_arg(a))) +#define pg_attribute_printf(f,a) __attribute__((format(PG_PRINTF_ATTRIBUTE, f, a))) +#else +#define pg_attribute_format_arg(a) +#define pg_attribute_printf(f,a) +#endif /* * Maximum length for identifiers (e.g. table names, column names, * function names). Names actually are limited to one less byte than this, diff --git a/src/include/pool_type.h b/src/include/pool_type.h index 34efbaf30..84013e6e6 100644 --- a/src/include/pool_type.h +++ b/src/include/pool_type.h @@ -29,6 +29,7 @@ #include "config.h" #include <sys/types.h> #include <sys/socket.h> +#include <stddef.h> #include "libpq-fe.h" /* Define common boolean type. C++ and BEOS already has it so exclude them. */ #ifdef c_plusplus @@ -57,6 +58,21 @@ typedef char bool; #endif /* not C++ */ #endif /* __BEOS__ */ +/* ---------------------------------------------------------------- + * Section 5: offsetof, lengthof, endof, alignment + * ---------------------------------------------------------------- + */ +/* + * offsetof + * Offset of a structure/union field within that structure/union. + * + * XXX This is supposed to be part of stddef.h, but isn't on + * some systems (like SunOS 4). + */ +#ifndef offsetof +#define offsetof(type, field) ((long) &((type *)0)->field) +#endif + #define PointerIsValid(pointer) ((const void*)(pointer) != NULL) typedef signed char int8; /* == 8 bits */ typedef signed short int16; /* == 16 bits */ diff --git a/src/include/utils/memnodes.h b/src/include/utils/memnodes.h index 553ccf8fd..4e15274b3 100644 --- a/src/include/utils/memnodes.h +++ b/src/include/utils/memnodes.h @@ -4,8 +4,7 @@ * POSTGRES memory context node definitions. * * - * Portions Copyright (c) 2003-2014, PgPool Global Development Group - * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/nodes/memnodes.h @@ -16,6 +15,23 @@ #define MEMNODES_H #include "parser/nodes.h" +/* + * MemoryContextCounters + * Summarization state for MemoryContextStats collection. + * + * The set of counters in this struct is biased towards AllocSet; if we ever + * add any context types that are based on fundamentally different approaches, + * we might need more or different counters here. A possible API spec then + * would be to print only nonzero counters, but for now we just summarize in + * the format historically used by AllocSet. + */ +typedef struct MemoryContextCounters +{ + Size nblocks; /* Total number of malloc blocks */ + Size freechunks; /* Total number of free chunks */ + Size totalspace; /* Total bytes requested from malloc */ + Size freespace; /* The unused portion of totalspace */ +} MemoryContextCounters; /* * MemoryContext @@ -45,21 +61,27 @@ typedef struct MemoryContextMethods void (*delete_context) (MemoryContext context); Size (*get_chunk_space) (MemoryContext context, void *pointer); bool (*is_empty) (MemoryContext context); - void (*stats) (MemoryContext context, int level); + void (*stats) (MemoryContext context, int level, bool print, + MemoryContextCounters *totals); #ifdef MEMORY_CONTEXT_CHECKING void (*check) (MemoryContext context); #endif } MemoryContextMethods; + typedef struct MemoryContextData { NodeTag type; /* identifies exact kind of context */ - MemoryContextMethods *methods; /* virtual function table */ + /* 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 prevchild; /* previous child of same parent */ MemoryContext nextchild; /* next child of same parent */ char *name; /* context name (just for debugging) */ - bool isReset; /* T = no space alloced since last reset */ + MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */ } MemoryContextData; /* utils/palloc.h contains typedef struct MemoryContextData *MemoryContext */ @@ -73,6 +95,6 @@ typedef struct MemoryContextData */ #define MemoryContextIsValid(context) \ ((context) != NULL && \ - (IsA((context), AllocSetContext))) + (IsA((context), AllocSetContext) || IsA((context), SlabContext))) -#endif /* MEMNODES_H */ +#endif /* MEMNODES_H */ diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index a40af1e66..0df8cd138 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -7,7 +7,7 @@ * of the API of the memory management subsystem. * * - * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/utils/memutils.h @@ -37,7 +37,7 @@ * MemoryContextAllocHuge(). Both limits permit code to assume that it may * compute twice an allocation's size without overflow. */ -#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */ +#define MaxAllocSize ((Size) 0x3fffffff) /* 1 gigabyte - 1 */ #define AllocSizeIsValid(size) ((Size) (size) <= MaxAllocSize) @@ -45,27 +45,6 @@ #define AllocHugeSizeIsValid(size) ((Size) (size) <= MaxAllocHugeSize) -/* - * All chunks allocated by any memory context manager are required to be - * preceded by a StandardChunkHeader at a spacing of STANDARDCHUNKHEADERSIZE. - * A currently-allocated chunk must contain a backpointer to its owning - * context as well as the allocated size of the chunk. The backpointer is - * used by pfree() and repalloc() to find the context to call. The allocated - * size is not absolutely essential, but it's expected to be needed by any - * reasonable implementation. - */ -typedef struct StandardChunkHeader -{ - MemoryContext context; /* owning context */ - Size size; /* size of data space allocated in chunk */ -#ifdef MEMORY_CONTEXT_CHECKING - /* when debugging memory usage, also store actual requested size */ - Size requested_size; -#endif -} StandardChunkHeader; - -#define STANDARDCHUNKHEADERSIZE MAXALIGN(sizeof(StandardChunkHeader)) - /* * Standard top-level memory contexts. @@ -79,10 +58,9 @@ extern MemoryContext ProcessLoopContext; extern MemoryContext CacheMemoryContext; extern MemoryContext MessageContext; extern MemoryContext QueryContext; -extern MemoryContext CurTransactionContext; -/* This is a transient link to the active portal's memory context: */ -extern PGDLLIMPORT MemoryContext PortalContext; +/* Backwards compatibility macro */ +#define MemoryContextResetAndDeleteChildren(ctx) MemoryContextReset(ctx) /* @@ -91,22 +69,60 @@ extern PGDLLIMPORT MemoryContext PortalContext; extern void MemoryContextInit(void); extern void MemoryContextReset(MemoryContext context); extern void MemoryContextDelete(MemoryContext context); +extern void MemoryContextResetOnly(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 MemoryContext MemoryContextGetParent(MemoryContext context); extern bool MemoryContextIsEmpty(MemoryContext context); extern void MemoryContextStats(MemoryContext context); +extern void MemoryContextStatsDetail(MemoryContext context, int max_children); +extern void MemoryContextAllowInCriticalSection(MemoryContext context, + bool allow); #ifdef MEMORY_CONTEXT_CHECKING extern void MemoryContextCheck(MemoryContext context); #endif extern bool MemoryContextContains(MemoryContext context, void *pointer); +/* + * GetMemoryChunkContext + * Given a currently-allocated chunk, determine the context + * it belongs to. + * + * All chunks allocated by any memory context manager are required to be + * preceded by the corresponding MemoryContext stored, without padding, in the + * preceding sizeof(void*) bytes. A currently-allocated chunk must contain a + * backpointer to its owning context. The backpointer is used by pfree() and + * repalloc() to find the context to call. + */ +#ifndef FRONTEND +static inline MemoryContext +GetMemoryChunkContext(void *pointer) +{ + MemoryContext context; + + /* + * 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 context. + */ + context = *(MemoryContext *) (((char *) pointer) - sizeof(void *)); + + AssertArg(MemoryContextIsValid(context)); + + return context; +} +#endif + /* * This routine handles the context-type-independent part of memory * context creation. It's intended to be called from context-type- @@ -129,6 +145,12 @@ extern MemoryContext AllocSetContextCreate(MemoryContext parent, Size initBlockSize, Size maxBlockSize); +/* slab.c */ +extern MemoryContext SlabContextCreate(MemoryContext parent, + const char *name, + Size blockSize, + Size chunkSize); + /* * Recommended default alloc parameters, suitable for "ordinary" contexts * that might hold quite a lot of data. @@ -136,13 +158,36 @@ extern MemoryContext AllocSetContextCreate(MemoryContext parent, #define ALLOCSET_DEFAULT_MINSIZE 0 #define ALLOCSET_DEFAULT_INITSIZE (8 * 1024) #define ALLOCSET_DEFAULT_MAXSIZE (8 * 1024 * 1024) +#define ALLOCSET_DEFAULT_SIZES \ + ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE /* - * Recommended alloc parameters for "small" contexts that are not expected + * Recommended alloc parameters for "small" contexts that are never expected * to contain much data (for example, a context to contain a query plan). */ #define ALLOCSET_SMALL_MINSIZE 0 #define ALLOCSET_SMALL_INITSIZE (1 * 1024) #define ALLOCSET_SMALL_MAXSIZE (8 * 1024) +#define ALLOCSET_SMALL_SIZES \ + ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE + +/* + * Recommended alloc parameters for contexts that should start out small, + * but might sometimes grow big. + */ +#define ALLOCSET_START_SMALL_SIZES \ + ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE + + +/* + * Threshold above which a request in an AllocSet context is certain to be + * allocated separately (and thereby have constant allocation overhead). + * Few callers should be interested in this, but tuplesort/tuplestore need + * to know it. + */ +#define ALLOCSET_SEPARATE_THRESHOLD 8192 + +#define SLAB_DEFAULT_BLOCK_SIZE (8 * 1024) +#define SLAB_LARGE_BLOCK_SIZE (8 * 1024 * 1024) -#endif /* MEMUTILS_H */ +#endif /* MEMUTILS_H */ diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h index 45d544e3f..4b6359ca8 100644 --- a/src/include/utils/palloc.h +++ b/src/include/utils/palloc.h @@ -6,9 +6,9 @@ * This file contains the basic memory allocation interface that is * needed by almost every backend module. It is included directly by * postgres.h, so the definitions here are automatically available - * everywhere. Keep it lean! + * everywhere. Keep it lean! * - * Memory allocation occurs within "contexts". Every chunk obtained from + * Memory allocation occurs within "contexts". Every chunk obtained from * palloc()/MemoryContextAlloc() is allocated within a specific context. * The entire contents of a context can be freed easily and quickly by * resetting or deleting the context --- this is both faster and less @@ -18,8 +18,7 @@ * everything that should be freed. See utils/mmgr/README for more info. * * - * Portions Copyright (c) 2003-2015, PgPool Global Development Group - * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * src/include/utils/palloc.h @@ -31,33 +30,57 @@ #include <stdarg.h> #include "parser/pg_config_manual.h" - /* - * Type MemoryContextData is declared in nodes/memnodes.h. Most users + * Type MemoryContextData is declared in nodes/memnodes.h. Most users * of memory allocation should just treat it as an abstract type, so we * do not provide the struct contents here. */ typedef struct MemoryContextData *MemoryContext; -#ifndef FRONTEND +/* + * 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; /* * CurrentMemoryContext is the default allocation context for palloc(). - * We declare it here so that palloc() can be a macro. Avoid accessing it - * directly! Instead, use MemoryContextSwitchTo() to change the setting. + * Avoid accessing it directly! Instead, use MemoryContextSwitchTo() + * to change the setting. */ extern PGDLLIMPORT MemoryContext CurrentMemoryContext; +/* + * Flags for MemoryContextAllocExtended. + */ +#define MCXT_ALLOC_HUGE 0x01 /* allow huge allocation (> 1 GB) */ +#define MCXT_ALLOC_NO_OOM 0x02 /* no failure if out-of-memory */ +#define MCXT_ALLOC_ZERO 0x04 /* zero allocated memory */ + /* * Fundamental memory-allocation operations (more are in utils/memutils.h) */ extern void *MemoryContextAlloc(MemoryContext context, Size size); extern void *MemoryContextAllocZero(MemoryContext context, Size size); extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size); +extern void *MemoryContextAllocExtended(MemoryContext context, + Size size, int flags); -/* Higher-limit allocators. */ -extern void *MemoryContextAllocHuge(MemoryContext context, Size size); -extern void *repalloc_huge(void *pointer, Size size); +extern void *palloc(Size size); +extern void *palloc0(Size size); +extern void *palloc_extended(Size size, int flags); +extern void *repalloc(void *pointer, Size size); +extern void pfree(void *pointer); /* * The result of palloc() is always word-aligned, so we can skip testing @@ -72,17 +95,19 @@ extern void *repalloc_huge(void *pointer, Size size); MemoryContextAllocZeroAligned(CurrentMemoryContext, sz) : \ MemoryContextAllocZero(CurrentMemoryContext, sz) ) +/* Higher-limit allocators. */ +extern void *MemoryContextAllocHuge(MemoryContext context, Size size); +extern void *repalloc_huge(void *pointer, Size size); + /* - * MemoryContextSwitchTo can't be a macro in standard C compilers. - * But we can make it an inline function if the compiler supports it. - * See STATIC_IF_INLINE in c.h. + * Although this header file is nominally backend-only, certain frontend + * programs like pg_controldata include it via postgres.h. For some compilers + * it's necessary to hide the inline definition of MemoryContextSwitchTo in + * this scenario; hence the #ifndef FRONTEND. */ -#ifndef PG_USE_INLINE -extern MemoryContext MemoryContextSwitchTo(MemoryContext context); -#endif /* !PG_USE_INLINE */ -#if defined(PG_USE_INLINE) || defined(MCXT_INCLUDE_DEFINITIONS) -STATIC_IF_INLINE MemoryContext +#ifndef FRONTEND +static inline MemoryContext MemoryContextSwitchTo(MemoryContext context) { MemoryContext old = CurrentMemoryContext; @@ -90,28 +115,24 @@ MemoryContextSwitchTo(MemoryContext context) CurrentMemoryContext = context; return old; } -#endif /* PG_USE_INLINE || MCXT_INCLUDE_DEFINITIONS */ +#endif /* FRONTEND */ + +/* Registration of memory context reset/delete callbacks */ +extern void MemoryContextRegisterResetCallback(MemoryContext context, + MemoryContextCallback *cb); /* * These are like standard strdup() except the copied string is * allocated in a context, not with malloc(). */ extern char *MemoryContextStrdup(MemoryContext context, const char *string); -#endif /* !FRONTEND */ - extern char *pstrdup(const char *in); extern char *pnstrdup(const char *in, Size len); -extern void *palloc(Size size); -extern void *palloc0(Size size); -extern void pfree(void *pointer); -extern void *repalloc(void *pointer, Size size); -/* sprintf into a palloc'd buffer --- these are in psprintf.c */ -extern char * -psprintf(const char *fmt,...) -__attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))); -extern size_t -pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) -__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0))); +extern char *pchomp(const char *in); + +/* sprintf into a palloc'd buffer --- these are in psprintf.c */ +extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2); +extern size_t pvsnprintf(char *buf, size_t len, const char *fmt, va_list args) pg_attribute_printf(3, 0); -#endif /* PALLOC_H */ +#endif /* PALLOC_H */ diff --git a/src/utils/mmgr/aset.c b/src/utils/mmgr/aset.c index 37b604937..7dbe589a6 100644 --- a/src/utils/mmgr/aset.c +++ b/src/utils/mmgr/aset.c @@ -7,7 +7,7 @@ * type. * * - * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION @@ -38,49 +38,9 @@ * request, even if it was much larger than necessary. This led to more * and more wasted space in allocated chunks over time. To fix, get rid * of the midrange behavior: we now handle only "small" power-of-2-size - * chunks as chunks. Anything "large" is passed off to malloc(). Change + * chunks as chunks. Anything "large" is passed off to malloc(). Change * the number of freelists to change the small/large boundary. * - * - * About CLOBBER_FREED_MEMORY: - * - * If this symbol is defined, all freed memory is overwritten with 0x7F's. - * This is useful for catching places that reference already-freed memory. - * - * About MEMORY_CONTEXT_CHECKING: - * - * Since we usually round request sizes up to the next power of 2, there - * is often some unused space immediately after a requested data area. - * Thus, if someone makes the common error of writing past what they've - * requested, the problem is likely to go unnoticed ... until the day when - * there *isn't* any wasted space, perhaps because of different memory - * alignment on a new platform, or some other effect. To catch this sort - * of problem, the MEMORY_CONTEXT_CHECKING option stores 0x7E just beyond - * the requested space whenever the request is less than the actual chunk - * size, and verifies that the byte is undamaged when the chunk is freed. - * - * - * About USE_VALGRIND and Valgrind client requests: - * - * Valgrind provides "client request" macros that exchange information with - * the host Valgrind (if any). Under !USE_VALGRIND, memdebug.h stubs out - * currently-used macros. - * - * When running under Valgrind, we want a NOACCESS memory region both before - * and after the allocation. The chunk header is tempting as the preceding - * region, but mcxt.c expects to able to examine the standard chunk header - * fields. Therefore, we use, when available, the requested_size field and - * any subsequent padding. requested_size is made NOACCESS before returning - * a chunk pointer to a caller. However, to reduce client request traffic, - * it is kept DEFINED in chunks on the free list. - * - * The rounded-up capacity of the chunk usually acts as a post-allocation - * NOACCESS region. If the request consumes precisely the entire chunk, - * there is no such region; another chunk header may immediately follow. In - * that case, Valgrind will not detect access beyond the end of the chunk. - * - * See also the cooperating Valgrind client requests in mcxt.c. - * *------------------------------------------------------------------------- */ @@ -91,6 +51,7 @@ #include "utils/elog.h" #include <string.h> #include <stdint.h> + /* Define this to detail debug alloc information */ /* #define HAVE_ALLOCINFO */ @@ -114,9 +75,9 @@ * * With the current parameters, request sizes up to 8K are treated as chunks, * larger requests go into dedicated blocks. Change ALLOCSET_NUM_FREELISTS - * to adjust the boundary point. (But in contexts with small maxBlockSize, - * we may set the allocChunkLimit to less than 8K, so as to avoid space - * wastage.) + * to adjust the boundary point; and adjust ALLOCSET_SEPARATE_THRESHOLD in + * memutils.h to agree. (Note: in contexts with small maxBlockSize, we may + * set the allocChunkLimit to less than 8K, so as to avoid space wastage.) *-------------------- */ @@ -139,22 +100,9 @@ */ #define ALLOC_BLOCKHDRSZ MAXALIGN(sizeof(AllocBlockData)) -#define ALLOC_CHUNKHDRSZ MAXALIGN(sizeof(AllocChunkData)) +#define ALLOC_CHUNKHDRSZ sizeof(struct AllocChunkData) -/* Portion of ALLOC_CHUNKHDRSZ examined outside aset.c. */ -#define ALLOC_CHUNK_PUBLIC \ - (offsetof(AllocChunkData, size) + sizeof(Size)) - -/* Portion of ALLOC_CHUNKHDRSZ excluding trailing padding. */ -#ifdef MEMORY_CONTEXT_CHECKING -#define ALLOC_CHUNK_USED \ - (offsetof(AllocChunkData, requested_size) + sizeof(Size)) -#else -#define ALLOC_CHUNK_USED \ - (offsetof(AllocChunkData, size) + sizeof(Size)) -#endif - -typedef struct AllocBlockData *AllocBlock; /* forward reference */ +typedef struct AllocBlockData *AllocBlock; /* forward reference */ typedef struct AllocChunkData *AllocChunk; /* @@ -177,7 +125,7 @@ typedef struct AllocSetContext MemoryContextData header; /* Standard memory-context fields */ /* Info about storage allocated in this context: */ AllocBlock blocks; /* head of list of blocks in this set */ - AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* free chunk lists */ + AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* free chunk lists */ /* Allocation parameters for this context: */ Size initBlockSize; /* initial block size */ Size maxBlockSize; /* maximum block size */ @@ -191,7 +139,7 @@ typedef AllocSetContext *AllocSet; /* * AllocBlock * An AllocBlock is the unit of memory that is obtained by aset.c - * from malloc(). It contains one or more AllocChunks, which are + * from malloc(). It contains one or more AllocChunks, which are * the units requested by palloc() and freed by pfree(). AllocChunks * cannot be returned to malloc() individually, instead they are put * on freelists by pfree() and re-used by the next palloc() that has @@ -203,29 +151,35 @@ typedef AllocSetContext *AllocSet; typedef struct AllocBlockData { AllocSet aset; /* aset that owns this block */ - AllocBlock next; /* next block in aset's blocks list */ + AllocBlock prev; /* prev block in aset's blocks list, if any */ + AllocBlock next; /* next block in aset's blocks list, if any */ char *freeptr; /* start of free space in this block */ char *endptr; /* end of space in this block */ -} AllocBlockData; +} AllocBlockData; /* * AllocChunk * The prefix of each piece of memory in an AllocBlock - * - * NB: this MUST match StandardChunkHeader as defined by utils/memutils.h. */ typedef struct AllocChunkData { - /* aset is the owning aset if allocated, or the freelist link if free */ - void *aset; /* size is always the size of the usable space in the chunk */ Size size; #ifdef MEMORY_CONTEXT_CHECKING /* when debugging memory usage, also store actual requested size */ /* this is zero in a free chunk */ Size requested_size; +#if MAXIMUM_ALIGNOF > 4 && SIZEOF_VOID_P == 4 + Size padding; #endif -} AllocChunkData; + +#endif /* MEMORY_CONTEXT_CHECKING */ + + /* aset is the owning aset if allocated, or the freelist link if free */ + void *aset; + + /* there must not be any padding to reach a MAXALIGN boundary here! */ +} AllocChunkData; /* * AllocPointerIsValid @@ -255,7 +209,8 @@ 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, int level); +static void AllocSetStats(MemoryContext context, int level, bool print, + MemoryContextCounters *totals); #ifdef MEMORY_CONTEXT_CHECKING static void AllocSetCheck(MemoryContext context); @@ -297,10 +252,10 @@ static const unsigned char LogTable256[256] = */ #ifdef HAVE_ALLOCINFO #define AllocFreeInfo(_cxt, _chunk) \ - fprintf(stderr, "AllocFree: %s: %p, %d\n", \ + fprintf(stderr, "AllocFree: %s: %p, %zu\n", \ (_cxt)->header.name, (_chunk), (_chunk)->size) #define AllocAllocInfo(_cxt, _chunk) \ - fprintf(stderr, "AllocAlloc: %s: %p, %d\n", \ + fprintf(stderr, "AllocAlloc: %s: %p, %zu\n", \ (_cxt)->header.name, (_chunk), (_chunk)->size) #else #define AllocFreeInfo(_cxt, _chunk) @@ -328,7 +283,7 @@ AllocSetFreeIndex(Size size) /* * At this point we need to obtain log2(tsize)+1, ie, the number of - * not-all-zero bits at the right. We used to do this with a + * not-all-zero bits at the right. We used to do this with a * shift-and-count loop, but this function is enough of a hotspot to * justify micro-optimization effort. The best approach seems to be * to use a lookup table. Note that this code assumes that @@ -346,77 +301,6 @@ AllocSetFreeIndex(Size size) return idx; } -#ifdef CLOBBER_FREED_MEMORY - -/* Wipe freed memory for debugging purposes */ -static void -wipe_mem(void *ptr, size_t size) -{ - VALGRIND_MAKE_MEM_UNDEFINED(ptr, size); - memset(ptr, 0x7F, size); - VALGRIND_MAKE_MEM_NOACCESS(ptr, size); -} -#endif - -#ifdef MEMORY_CONTEXT_CHECKING -static void -set_sentinel(void *base, Size offset) -{ - char *ptr = (char *) base + offset; - - VALGRIND_MAKE_MEM_UNDEFINED(ptr, 1); - *ptr = 0x7E; - VALGRIND_MAKE_MEM_NOACCESS(ptr, 1); -} - -static bool -sentinel_ok(const void *base, Size offset) -{ - const char *ptr = (const char *) base + offset; - bool ret; - - VALGRIND_MAKE_MEM_DEFINED(ptr, 1); - ret = *ptr == 0x7E; - VALGRIND_MAKE_MEM_NOACCESS(ptr, 1); - - return ret; -} -#endif - -#ifdef RANDOMIZE_ALLOCATED_MEMORY - -/* - * Fill a just-allocated piece of memory with "random" data. It's not really - * very random, just a repeating sequence with a length that's prime. What - * we mainly want out of it is to have a good probability that two palloc's - * of the same number of bytes start out containing different data. - * - * The region may be NOACCESS, so make it UNDEFINED first to avoid errors as - * we fill it. Filling the region makes it DEFINED, so make it UNDEFINED - * again afterward. Whether to finally make it UNDEFINED or NOACCESS is - * fairly arbitrary. UNDEFINED is more convenient for AllocSetRealloc(), and - * other callers have no preference. - */ -static void -randomize_mem(char *ptr, size_t size) -{ - static int save_ctr = 1; - size_t remaining = size; - int ctr; - - ctr = save_ctr; - VALGRIND_MAKE_MEM_UNDEFINED(ptr, size); - while (remaining-- > 0) - { - *ptr++ = ctr; - if (++ctr > 251) - ctr = 1; - } - VALGRIND_MAKE_MEM_UNDEFINED(ptr - size, size); - save_ctr = ctr; -} -#endif /* RANDOMIZE_ALLOCATED_MEMORY */ - /* * Public routines @@ -428,10 +312,14 @@ randomize_mem(char *ptr, size_t size) * Create a new AllocSet context. * * parent: parent context, or NULL if top-level context - * name: name of context (for debugging --- string will be copied) + * name: name of context (for debugging only, need not be unique) * minContextSize: minimum context size * initBlockSize: initial allocation block size * maxBlockSize: maximum allocation block size + * + * Notes: the name string will be copied into context-lifespan storage. + * Most callers should abstract the context size parameters using a macro + * such as ALLOCSET_DEFAULT_SIZES. */ MemoryContext AllocSetContextCreate(MemoryContext parent, @@ -440,30 +328,43 @@ AllocSetContextCreate(MemoryContext parent, Size initBlockSize, Size maxBlockSize) { - AllocSet context; + AllocSet set; - /* Do the type-independent part of context creation */ - context = (AllocSet) MemoryContextCreate(T_AllocSetContext, - sizeof(AllocSetContext), - &AllocSetMethods, - parent, - name); + StaticAssertStmt(offsetof(AllocChunkData, aset) + sizeof(MemoryContext) == + MAXALIGN(sizeof(AllocChunkData)), + "padding calculation in AllocChunkData is wrong"); /* - * Make sure alloc parameters are reasonable, and save them. - * - * We somewhat arbitrarily enforce a minimum 1K block size. + * First, validate allocation parameters. (If we're going to throw an + * error, we should do so before the context is created, not after.) We + * somewhat arbitrarily enforce a minimum 1K block size. */ - initBlockSize = MAXALIGN(initBlockSize); - if (initBlockSize < 1024) - initBlockSize = 1024; - maxBlockSize = MAXALIGN(maxBlockSize); - if (maxBlockSize < initBlockSize) - maxBlockSize = initBlockSize; - Assert(AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */ - context->initBlockSize = initBlockSize; - context->maxBlockSize = maxBlockSize; - context->nextBlockSize = initBlockSize; + if (initBlockSize != MAXALIGN(initBlockSize) || + initBlockSize < 1024) + elog(ERROR, "invalid initBlockSize for memory context: %zu", + initBlockSize); + if (maxBlockSize != MAXALIGN(maxBlockSize) || + maxBlockSize < initBlockSize || + !AllocHugeSizeIsValid(maxBlockSize)) /* must be safe to double */ + elog(ERROR, "invalid maxBlockSize for memory context: %zu", + maxBlockSize); + if (minContextSize != 0 && + (minContextSize != MAXALIGN(minContextSize) || + minContextSize <= ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ)) + elog(ERROR, "invalid minContextSize for memory context: %zu", + minContextSize); + + /* Do the type-independent part of context creation */ + set = (AllocSet) MemoryContextCreate(T_AllocSetContext, + sizeof(AllocSetContext), + &AllocSetMethods, + parent, + name); + + /* Save allocation parameters */ + set->initBlockSize = initBlockSize; + set->maxBlockSize = maxBlockSize; + set->nextBlockSize = initBlockSize; /* * Compute the allocation chunk size limit for this context. It can't be @@ -478,18 +379,23 @@ AllocSetContextCreate(MemoryContext parent, * We have to have allocChunkLimit a power of two, because the requested * and actually-allocated sizes of any chunk must be on the same side of * the limit, else we get confused about whether the chunk is "big". + * + * Also, allocChunkLimit must not exceed ALLOCSET_SEPARATE_THRESHOLD. */ - context->allocChunkLimit = ALLOC_CHUNK_LIMIT; - while ((Size) (context->allocChunkLimit + ALLOC_CHUNKHDRSZ) > + StaticAssertStmt(ALLOC_CHUNK_LIMIT == ALLOCSET_SEPARATE_THRESHOLD, + "ALLOC_CHUNK_LIMIT != ALLOCSET_SEPARATE_THRESHOLD"); + + set->allocChunkLimit = ALLOC_CHUNK_LIMIT; + while ((Size) (set->allocChunkLimit + ALLOC_CHUNKHDRSZ) > (Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) / ALLOC_CHUNK_FRACTION)) - context->allocChunkLimit >>= 1; + set->allocChunkLimit >>= 1; /* * Grab always-allocated space, if requested */ - if (minContextSize > ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ) + if (minContextSize > 0) { - Size blksize = MAXALIGN(minContextSize); + Size blksize = minContextSize; AllocBlock block; block = (AllocBlock) malloc(blksize); @@ -502,20 +408,23 @@ AllocSetContextCreate(MemoryContext parent, errdetail("Failed while creating memory context \"%s\".", name))); } - block->aset = context; + block->aset = set; block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; block->endptr = ((char *) block) + blksize; - block->next = context->blocks; - context->blocks = block; + block->prev = NULL; + block->next = set->blocks; + if (block->next) + block->next->prev = block; + set->blocks = block; /* Mark block as not to be released at reset time */ - context->keeper = block; + set->keeper = block; /* Mark unallocated space NOACCESS; leave the block header alone. */ VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, blksize - ALLOC_BLOCKHDRSZ); } - return (MemoryContext) context; + return (MemoryContext) set; } /* @@ -546,7 +455,7 @@ AllocSetInit(MemoryContext context) * Actually, this routine has some discretion about what to do. * It should mark all allocated chunks freed, but it need not necessarily * give back all the resources the set owns. Our actual implementation is - * that we hang onto any "keeper" block specified for the set. In this way, + * that we hang onto any "keeper" block specified for the set. In this way, * we don't thrash malloc() when a context is repeatedly reset after small * allocations, which is typical behavior for per-tuple contexts. */ @@ -587,6 +496,7 @@ AllocSetReset(MemoryContext context) VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart); #endif block->freeptr = datastart; + block->prev = NULL; block->next = NULL; } else @@ -644,8 +554,8 @@ AllocSetDelete(MemoryContext context) /* * AllocSetAlloc - * Returns pointer to allocated memory of given size; memory is added - * to the set. + * Returns pointer to allocated memory of given size or NULL if + * request could not be completed; memory is added to the set. * * No request may exceed: * MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ @@ -673,14 +583,7 @@ AllocSetAlloc(MemoryContext context, Size size) blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; block = (AllocBlock) malloc(blksize); if (block == NULL) - { - MemoryContextStats(TopMemoryContext); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"), - errdetail("Failed on request of size %lu.", - (unsigned long) size))); - } + return NULL; block->aset = set; block->freeptr = block->endptr = ((char *) block) + blksize; @@ -700,16 +603,20 @@ AllocSetAlloc(MemoryContext context, Size size) #endif /* - * Stick the new block underneath the active allocation block, so that - * we don't lose the use of the space remaining therein. + * Stick the new block underneath the active allocation block, if any, + * so that we don't lose the use of the space remaining therein. */ if (set->blocks != NULL) { + block->prev = set->blocks; block->next = set->blocks->next; + if (block->next) + block->next->prev = block; set->blocks->next = block; } else { + block->prev = NULL; block->next = NULL; set->blocks = block; } @@ -717,13 +624,13 @@ AllocSetAlloc(MemoryContext context, Size size) AllocAllocInfo(set, chunk); /* - * Chunk header public fields remain DEFINED. The requested - * allocation itself can be NOACCESS or UNDEFINED; our caller will - * soon make it UNDEFINED. Make extra space at the end of the chunk, - * if any, NOACCESS. + * Chunk's metadata fields remain DEFINED. The requested allocation + * itself can be NOACCESS or UNDEFINED; our caller will soon make it + * UNDEFINED. Make extra space at the end of the chunk, if any, + * NOACCESS. */ - VALGRIND_MAKE_MEM_NOACCESS((char *) chunk + ALLOC_CHUNK_PUBLIC, - chunk_size + ALLOC_CHUNKHDRSZ - ALLOC_CHUNK_PUBLIC); + VALGRIND_MAKE_MEM_NOACCESS((char *) chunk + ALLOC_CHUNKHDRSZ, + chunk_size - ALLOC_CHUNKHDRSZ); return AllocChunkGetPointer(chunk); } @@ -797,7 +704,7 @@ AllocSetAlloc(MemoryContext context, Size size) /* * In most cases, we'll get back the index of the next larger - * freelist than the one we need to put this chunk on. The + * freelist than the one we need to put this chunk on. The * exception is when availchunk is exactly a power of 2. */ if (availchunk != ((Size) 1 << (a_fidx + ALLOC_MINBITS))) @@ -810,14 +717,14 @@ AllocSetAlloc(MemoryContext context, Size size) chunk = (AllocChunk) (block->freeptr); /* Prepare to initialize the chunk header. */ - VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNK_USED); + VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNKHDRSZ); block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ); availspace -= (availchunk + ALLOC_CHUNKHDRSZ); chunk->size = availchunk; #ifdef MEMORY_CONTEXT_CHECKING - chunk->requested_size = 0; /* mark it free */ + chunk->requested_size = 0; /* mark it free */ #endif chunk->aset = (void *) set->freelist[a_fidx]; set->freelist[a_fidx] = chunk; @@ -868,14 +775,7 @@ AllocSetAlloc(MemoryContext context, Size size) } if (block == NULL) - { - MemoryContextStats(TopMemoryContext); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"), - errdetail("Failed on request of size %lu.", - (unsigned long) size))); - } + return NULL; block->aset = set; block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; @@ -897,7 +797,10 @@ AllocSetAlloc(MemoryContext context, Size size) VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, blksize - ALLOC_BLOCKHDRSZ); + block->prev = NULL; block->next = set->blocks; + if (block->next) + block->next->prev = block; set->blocks = block; } @@ -907,7 +810,7 @@ AllocSetAlloc(MemoryContext context, Size size) chunk = (AllocChunk) (block->freeptr); /* Prepare to initialize the chunk header. */ - VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNK_USED); + VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNKHDRSZ); block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ); Assert(block->freeptr <= block->endptr); @@ -957,29 +860,28 @@ AllocSetFree(MemoryContext context, void *pointer) { /* * Big chunks are certain to have been allocated as single-chunk - * blocks. Find the containing block and return it to malloc(). + * blocks. Just unlink that block and return it to malloc(). */ - AllocBlock block = set->blocks; - AllocBlock prevblock = NULL; + AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ); - while (block != NULL) - { - if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ)) - break; - prevblock = block; - block = block->next; - } - if (block == NULL) + /* + * Try to verify that we have a sane block pointer: it should + * reference the correct aset, and freeptr and endptr should point + * just past the chunk. + */ + if (block->aset != set || + block->freeptr != block->endptr || + block->freeptr != ((char *) block) + + (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ)) elog(ERROR, "could not find block containing chunk %p", chunk); - /* let's just make sure chunk is the only one in the block */ - Assert(block->freeptr == ((char *) block) + - (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ)); /* OK, remove block from aset's list and free it */ - if (prevblock == NULL) - set->blocks = block->next; + if (block->prev) + block->prev->next = block->next; else - prevblock->next = block->next; + set->blocks = block->next; + if (block->next) + block->next->prev = block->prev; #ifdef CLOBBER_FREED_MEMORY wipe_mem(block, block->freeptr - ((char *) block)); #endif @@ -1006,9 +908,10 @@ AllocSetFree(MemoryContext context, void *pointer) /* * AllocSetRealloc - * Returns new pointer to allocated memory of given size; this memory - * is added to the set. Memory associated with given pointer is copied - * into the new memory, and the old memory is freed. + * Returns new pointer to allocated memory of given size or NULL if + * request could not be completed; this memory is added to the set. + * Memory associated with given pointer is copied into the new memory, + * and the old memory is freed. * * Without MEMORY_CONTEXT_CHECKING, we don't know the old request size. This * makes our Valgrind client requests less-precise, hazarding false negatives. @@ -1084,50 +987,42 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) if (oldsize > set->allocChunkLimit) { /* - * The chunk must have been allocated as a single-chunk block. Find - * the containing block and use realloc() to make it bigger with - * minimum space wastage. + * The chunk must have been allocated as a single-chunk block. Use + * realloc() to make the containing block bigger with minimum space + * wastage. */ - AllocBlock block = set->blocks; - AllocBlock prevblock = NULL; + AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ); Size chksize; Size blksize; - while (block != NULL) - { - if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ)) - break; - prevblock = block; - block = block->next; - } - if (block == NULL) + /* + * Try to verify that we have a sane block pointer: it should + * reference the correct aset, and freeptr and endptr should point + * just past the chunk. + */ + if (block->aset != set || + block->freeptr != block->endptr || + block->freeptr != ((char *) block) + + (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ)) elog(ERROR, "could not find block containing chunk %p", chunk); - /* let's just make sure chunk is the only one in the block */ - Assert(block->freeptr == ((char *) block) + - (chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ)); /* Do the realloc */ chksize = MAXALIGN(size); blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; block = (AllocBlock) realloc(block, blksize); if (block == NULL) - { - MemoryContextStats(TopMemoryContext); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"), - errdetail("Failed on request of size %lu.", - (unsigned long) size))); - } + return NULL; block->freeptr = block->endptr = ((char *) block) + blksize; /* Update pointers since block has likely been moved */ chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ); pointer = AllocChunkGetPointer(chunk); - if (prevblock == NULL) - set->blocks = block; + if (block->prev) + block->prev->next = block; else - prevblock->next = block; + set->blocks = block; + if (block->next) + block->next->prev = block; chunk->size = chksize; #ifdef MEMORY_CONTEXT_CHECKING @@ -1151,7 +1046,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) /* set mark to catch clobber of "unused" space */ if (size < chunk->size) - set_sentinel(AllocChunkGetPointer(chunk), size); + set_sentinel(pointer, size); #else /* !MEMORY_CONTEXT_CHECKING */ /* @@ -1164,7 +1059,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) /* Make any trailing alignment padding NOACCESS. */ VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - size); - return AllocChunkGetPointer(chunk); + + return pointer; } else { @@ -1184,6 +1080,10 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) /* allocate new chunk */ newPointer = AllocSetAlloc((MemoryContext) set, size); + /* leave immediately if request was not completed */ + if (newPointer == NULL) + return NULL; + /* * AllocSetAlloc() just made the region NOACCESS. Change it to * UNDEFINED for the moment; memcpy() will then transfer definedness @@ -1241,20 +1141,23 @@ AllocSetIsEmpty(MemoryContext context) /* * AllocSetStats - * Displays stats about memory consumption of an allocset. + * Compute stats about memory consumption of an allocset. + * + * level: recursion level (0 at top level); used for print indentation. + * print: true to print stats to stderr. + * totals: if not NULL, add stats about this allocset into *totals. */ static void -AllocSetStats(MemoryContext context, int level) +AllocSetStats(MemoryContext context, int level, bool print, + MemoryContextCounters *totals) { AllocSet set = (AllocSet) context; - long nblocks = 0; - long nchunks = 0; - long totalspace = 0; - long freespace = 0; + Size nblocks = 0; + Size freechunks = 0; + Size totalspace = 0; + Size freespace = 0; AllocBlock block; - AllocChunk chunk; int fidx; - int i; for (block = set->blocks; block != NULL; block = block->next) { @@ -1264,21 +1167,35 @@ AllocSetStats(MemoryContext context, int level) } for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++) { + AllocChunk chunk; + for (chunk = set->freelist[fidx]; chunk != NULL; chunk = (AllocChunk) chunk->aset) { - nchunks++; + freechunks++; freespace += chunk->size + ALLOC_CHUNKHDRSZ; } } - for (i = 0; i < level; i++) - fprintf(stderr, " "); + if (print) + { + int i; + + for (i = 0; i < level; i++) + fprintf(stderr, " "); + fprintf(stderr, + "%s: %zu total in %zd blocks; %zu free (%zd chunks); %zu used\n", + set->header.name, totalspace, nblocks, freespace, freechunks, + totalspace - freespace); + } - fprintf(stderr, - "%s: %lu total in %ld blocks; %lu free (%ld chunks); %lu used\n", - set->header.name, totalspace, nblocks, freespace, nchunks, - totalspace - freespace); + if (totals) + { + totals->nblocks += nblocks; + totals->freechunks += freechunks; + totals->totalspace += totalspace; + totals->freespace += freespace; + } } @@ -1297,9 +1214,12 @@ AllocSetCheck(MemoryContext context) { AllocSet set = (AllocSet) context; char *name = set->header.name; + AllocBlock prevblock; AllocBlock block; - for (block = set->blocks; block != NULL; block = block->next) + for (prevblock = NULL, block = set->blocks; + block != NULL; + prevblock = block, block = block->next) { char *bpoz = ((char *) block) + ALLOC_BLOCKHDRSZ; long blk_used = block->freeptr - bpoz; @@ -1316,6 +1236,16 @@ AllocSetCheck(MemoryContext context) name, block); } + /* + * Check block header fields + */ + if (block->aset != set || + block->prev != prevblock || + block->freeptr < bpoz || + block->freeptr > block->endptr) + elog(WARNING, "problem in alloc set %s: corrupt header in block %p", + name, block); + /* * Chunk walker */ @@ -1325,10 +1255,10 @@ AllocSetCheck(MemoryContext context) Size chsize, dsize; - chsize = chunk->size; /* aligned chunk size */ + chsize = chunk->size; /* aligned chunk size */ VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size, sizeof(chunk->requested_size)); - dsize = chunk->requested_size; /* real data */ + dsize = chunk->requested_size; /* real data */ if (dsize > 0) /* not on a free list */ VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size, sizeof(chunk->requested_size)); @@ -1340,8 +1270,8 @@ AllocSetCheck(MemoryContext context) elog(WARNING, "problem in alloc set %s: req size > alloc size for chunk %p in block %p", name, chunk, block); if (chsize < (1 << ALLOC_MINBITS)) - elog(WARNING, "problem in alloc set %s: bad size %lu for chunk %p in block %p", - name, (unsigned long) chsize, chunk, block); + elog(WARNING, "problem in alloc set %s: bad size %zu for chunk %p in block %p", + name, chsize, chunk, block); /* single-chunk block? */ if (chsize > set->allocChunkLimit && @@ -1378,4 +1308,4 @@ AllocSetCheck(MemoryContext context) } } -#endif /* MEMORY_CONTEXT_CHECKING */ +#endif /* MEMORY_CONTEXT_CHECKING */ diff --git a/src/utils/mmgr/mcxt.c b/src/utils/mmgr/mcxt.c index 0eae52012..7eb3d0954 100644 --- a/src/utils/mmgr/mcxt.c +++ b/src/utils/mmgr/mcxt.c @@ -9,7 +9,7 @@ * context's MemoryContextMethods struct. * * - * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * @@ -19,9 +19,6 @@ *------------------------------------------------------------------------- */ -/* see palloc.h. Must be before postgres.h */ -#define MCXT_INCLUDE_DEFINITIONS - #include <stdint.h> #include <string.h> #include "pool_type.h" @@ -52,13 +49,20 @@ MemoryContext ProcessLoopContext = NULL; /* This context resets at every main lo MemoryContext CacheMemoryContext = NULL; MemoryContext MessageContext = NULL; MemoryContext QueryContext = NULL; -MemoryContext CurTransactionContext = NULL; -/* This is a transient link to the active portal's memory context: */ -MemoryContext PortalContext = NULL; -static void MemoryContextStatsInternal(MemoryContext context, int level); +static void MemoryContextCallResetCallbacks(MemoryContext context); +static void MemoryContextStatsInternal(MemoryContext context, int level, + bool print, int max_children, + MemoryContextCounters *totals); +/* + * You should not do memory allocations within a critical section, because + * an out-of-memory error will be escalated to a PANIC. To enforce that + * rule, the allocation functions Assert that. + */ +#define AssertNotInCriticalSection(context) \ + Assert(CritSectionCount == 0 || (context)->allowInCritSection) /***************************************************************************** * EXPORTED ROUTINES * @@ -76,7 +80,8 @@ static void MemoryContextStatsInternal(MemoryContext context, int level); * 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). + * by virtue of being forked off the postmaster). But in an EXEC_BACKEND + * build, each process must do this for itself. * * In a standalone backend this must be called during backend startup. */ @@ -86,16 +91,13 @@ 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.) + * First, initialize TopMemoryContext, which will hold the MemoryContext + * nodes for all other contexts. (There is special-case code in + * MemoryContextCreate() to handle this call.) */ TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", - 0, - 8 * 1024, - 8 * 1024); + ALLOCSET_DEFAULT_SIZES); /* * Not having any other place to point CurrentMemoryContext, make it point @@ -109,22 +111,25 @@ MemoryContextInit(void) * 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! + * 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. + * + * This should be the last step in this function, as elog.c assumes memory + * management works once ErrorContext is non-null. */ ErrorContext = AllocSetContextCreate(TopMemoryContext, "ErrorContext", 8 * 1024, 8 * 1024, 8 * 1024); + MemoryContextAllowInCriticalSection(ErrorContext, 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. + * Release all space allocated within a context and delete all its + * descendant contexts (but not the named context itself). */ void MemoryContextReset(MemoryContext context) @@ -133,11 +138,27 @@ MemoryContextReset(MemoryContext context) /* save a function call in common case where there are no children */ if (context->firstchild != NULL) - MemoryContextResetChildren(context); + MemoryContextDeleteChildren(context); + + /* save a function call if no pallocs since startup or last reset */ + if (!context->isReset) + MemoryContextResetOnly(context); +} + +/* + * MemoryContextResetOnly + * Release all space allocated within a context. + * Nothing is done to the context's descendant contexts. + */ +void +MemoryContextResetOnly(MemoryContext context) +{ + AssertArg(MemoryContextIsValid(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); @@ -159,7 +180,10 @@ MemoryContextResetChildren(MemoryContext context) AssertArg(MemoryContextIsValid(context)); for (child = context->firstchild; child != NULL; child = child->nextchild) - MemoryContextReset(child); + { + MemoryContextResetChildren(child); + MemoryContextResetOnly(child); + } } /* @@ -169,7 +193,7 @@ MemoryContextResetChildren(MemoryContext context) * * 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 + * as well as recurse to get the children. We must also delink the * node from its parent, if it has one. */ void @@ -183,6 +207,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 @@ -214,20 +246,53 @@ MemoryContextDeleteChildren(MemoryContext context) } /* - * MemoryContextResetAndDeleteChildren - * Release all space allocated within a context and delete all - * its descendants. + * 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. * - * This is a common combination case where we want to preserve the - * specific context but get rid of absolutely everything under it. + * 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 -MemoryContextResetAndDeleteChildren(MemoryContext context) +MemoryContextRegisterResetCallback(MemoryContext context, + MemoryContextCallback *cb) { AssertArg(MemoryContextIsValid(context)); - MemoryContextDeleteChildren(context); - MemoryContextReset(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); + } } /* @@ -254,26 +319,25 @@ MemoryContextSetParent(MemoryContext context, MemoryContext new_parent) AssertArg(MemoryContextIsValid(context)); AssertArg(context != new_parent); + /* Fast path if it's got correct parent already */ + if (new_parent == context->parent) + return; + /* Delink from existing parent, if any */ if (context->parent) { MemoryContext parent = context->parent; - if (context == parent->firstchild) - parent->firstchild = context->nextchild; + if (context->prevchild != NULL) + context->prevchild->nextchild = context->nextchild; else { - MemoryContext child; - - for (child = parent->firstchild; child; child = child->nextchild) - { - if (context == child->nextchild) - { - child->nextchild = context->nextchild; - break; - } - } + Assert(parent->firstchild == context); + parent->firstchild = context->nextchild; } + + if (context->nextchild != NULL) + context->nextchild->prevchild = context->prevchild; } /* And relink */ @@ -281,16 +345,39 @@ MemoryContextSetParent(MemoryContext context, MemoryContext new_parent) { AssertArg(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; } } +/* + * MemoryContextAllowInCriticalSection + * Allow/disallow allocations in this memory context within a critical + * section. + * + * Normally, memory allocations are not allowed within a critical section, + * because a failure would lead to PANIC. There are a few exceptions to + * that, like allocations related to debugging code that is not supposed to + * be enabled in production. This function can be used to exempt specific + * memory contexts from the assertion in palloc(). + */ +void +MemoryContextAllowInCriticalSection(MemoryContext context, bool allow) +{ + AssertArg(MemoryContextIsValid(context)); + + context->allowInCritSection = allow; +} + /* * GetMemoryChunkSpace * Given a currently-allocated chunk, determine the total space @@ -302,55 +389,10 @@ MemoryContextSetParent(MemoryContext context, MemoryContext new_parent) 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; + MemoryContext context = GetMemoryChunkContext(pointer); - /* - * 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; + return (context->methods->get_chunk_space) (context, + pointer); } /* @@ -388,25 +430,106 @@ MemoryContextIsEmpty(MemoryContext 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. + * This is just a debugging utility, so it's not very fancy. However, we do + * make some effort to summarize when the output would otherwise be very long. + * The statistics are sent to stderr. */ void MemoryContextStats(MemoryContext context) { - MemoryContextStatsInternal(context, 0); + /* A hard-wired limit on the number of children is usually good enough */ + MemoryContextStatsDetail(context, 100); +} + +/* + * MemoryContextStatsDetail + * + * Entry point for use if you want to vary the number of child contexts shown. + */ +void +MemoryContextStatsDetail(MemoryContext context, int max_children) +{ + MemoryContextCounters grand_totals; + + memset(&grand_totals, 0, sizeof(grand_totals)); + + MemoryContextStatsInternal(context, 0, true, max_children, &grand_totals); + + fprintf(stderr, + "Grand total: %zu bytes in %zd blocks; %zu free (%zd chunks); %zu used\n", + grand_totals.totalspace, grand_totals.nblocks, + grand_totals.freespace, grand_totals.freechunks, + grand_totals.totalspace - grand_totals.freespace); } +/* + * MemoryContextStatsInternal + * One recursion level for MemoryContextStats + * + * Print this context if print is true, but in any case accumulate counts into + * *totals (if given). + */ static void -MemoryContextStatsInternal(MemoryContext context, int level) +MemoryContextStatsInternal(MemoryContext context, int level, + bool print, int max_children, + MemoryContextCounters *totals) { + MemoryContextCounters local_totals; MemoryContext child; + int ichild; AssertArg(MemoryContextIsValid(context)); - (*context->methods->stats) (context, level); - for (child = context->firstchild; child != NULL; child = child->nextchild) - MemoryContextStatsInternal(child, level + 1); + /* Examine the context itself */ + (*context->methods->stats) (context, level, print, totals); + + /* + * Examine children. If there are more than max_children of them, we do + * not print the rest explicitly, but just summarize them. + */ + memset(&local_totals, 0, sizeof(local_totals)); + + for (child = context->firstchild, ichild = 0; + child != NULL; + child = child->nextchild, ichild++) + { + if (ichild < max_children) + MemoryContextStatsInternal(child, level + 1, + print, max_children, + totals); + else + MemoryContextStatsInternal(child, level + 1, + false, max_children, + &local_totals); + } + + /* Deal with excess children */ + if (ichild > max_children) + { + if (print) + { + int i; + + for (i = 0; i <= level; i++) + fprintf(stderr, " "); + fprintf(stderr, + "%d more child contexts containing %zu total in %zd blocks; %zu free (%zd chunks); %zu used\n", + ichild - max_children, + 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; + } + } } /* @@ -443,9 +566,13 @@ MemoryContextCheck(MemoryContext context) bool MemoryContextContains(MemoryContext context, void *pointer) { - StandardChunkHeader *header; + MemoryContext ptr_context; /* + * NB: Can't use GetMemoryChunkContext() here - that performs assertions + * that aren't acceptable here since we might be passed memory not + * allocated by any memory context. + * * 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. @@ -454,12 +581,11 @@ MemoryContextContains(MemoryContext context, void *pointer) return false; /* - * OK, it's probably safe to look at the chunk header. + * OK, it's probably safe to look at the context. */ - header = (StandardChunkHeader *) - ((char *) pointer - STANDARDCHUNKHEADERSIZE); + ptr_context = *(MemoryContext *) (((char *) pointer) - sizeof(void *)); - return header->context == context; + return ptr_context == context; } /*-------------------- @@ -473,22 +599,22 @@ MemoryContextContains(MemoryContext context, void *pointer) * 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(), + * 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, + * 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). + * 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 + * 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 + * 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. @@ -500,7 +626,7 @@ MemoryContextContains(MemoryContext context, void *pointer) * * 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 + * 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. @@ -519,6 +645,9 @@ MemoryContextCreate(NodeTag tag, Size size, MemoryContext node; Size needed = size + strlen(name) + 1; + /* creating new memory contexts is not allowed in a critical section */ + Assert(CritSectionCount == 0); + /* Get space for node and name */ if (TopMemoryContext != NULL) { @@ -539,6 +668,7 @@ MemoryContextCreate(NodeTag tag, Size size, node->methods = methods; node->parent = NULL; /* for the moment */ node->firstchild = NULL; + node->prevchild = NULL; node->nextchild = NULL; node->isReset = true; node->name = ((char *) node) + size; @@ -553,7 +683,11 @@ MemoryContextCreate(NodeTag tag, Size size, { node->parent = parent; node->nextchild = parent->firstchild; + if (parent->firstchild != NULL) + parent->firstchild->prevchild = node; parent->firstchild = node; + /* inherit allowInCritSection flag from parent */ + node->allowInCritSection = parent->allowInCritSection; } VALGRIND_CREATE_MEMPOOL(node, 0, false); @@ -575,14 +709,23 @@ MemoryContextAlloc(MemoryContext context, Size size) void *ret; AssertArg(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); if (!AllocSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); + elog(ERROR, "invalid memory alloc request size %zu", size); context->isReset = false; ret = (*context->methods->alloc) (context, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_ALLOC(context, ret, size); return ret; @@ -601,14 +744,23 @@ MemoryContextAllocZero(MemoryContext context, Size size) void *ret; AssertArg(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); if (!AllocSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); + elog(ERROR, "invalid memory alloc request size %zu", size); context->isReset = false; ret = (*context->methods->alloc) (context, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_ALLOC(context, ret, size); MemSetAligned(ret, 0, size); @@ -629,14 +781,23 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size) void *ret; AssertArg(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); if (!AllocSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); + elog(ERROR, "invalid memory alloc request size %zu", size); context->isReset = false; ret = (*context->methods->alloc) (context, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_ALLOC(context, ret, size); MemSetLoop(ret, 0, size); @@ -644,6 +805,46 @@ MemoryContextAllocZeroAligned(MemoryContext context, Size size) return ret; } +/* + * MemoryContextAllocExtended + * Allocate space within the specified context using the given flags. + */ +void * +MemoryContextAllocExtended(MemoryContext context, Size size, int flags) +{ + void *ret; + + AssertArg(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); + + if (((flags & MCXT_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(size)) || + ((flags & MCXT_ALLOC_HUGE) == 0 && !AllocSizeIsValid(size))) + elog(ERROR, "invalid memory alloc request size %zu", size); + + context->isReset = false; + + ret = (*context->methods->alloc) (context, size); + if (ret == NULL) + { + 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.", size))); + } + return NULL; + } + + VALGRIND_MEMPOOL_ALLOC(context, ret, size); + + if ((flags & MCXT_ALLOC_ZERO) != 0) + MemSetAligned(ret, 0, size); + + return ret; +} + void * palloc(Size size) { @@ -651,14 +852,23 @@ palloc(Size size) void *ret; AssertArg(MemoryContextIsValid(CurrentMemoryContext)); + AssertNotInCriticalSection(CurrentMemoryContext); if (!AllocSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); + elog(ERROR, "invalid memory alloc request size %zu", size); CurrentMemoryContext->isReset = false; ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size); return ret; @@ -671,14 +881,23 @@ palloc0(Size size) void *ret; AssertArg(MemoryContextIsValid(CurrentMemoryContext)); + AssertNotInCriticalSection(CurrentMemoryContext); if (!AllocSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); + elog(ERROR, "invalid memory alloc request size %zu", size); CurrentMemoryContext->isReset = false; ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size); MemSetAligned(ret, 0, size); @@ -686,6 +905,43 @@ palloc0(Size size) return ret; } +void * +palloc_extended(Size size, int flags) +{ + /* duplicates MemoryContextAllocExtended to avoid increased overhead */ + void *ret; + + AssertArg(MemoryContextIsValid(CurrentMemoryContext)); + AssertNotInCriticalSection(CurrentMemoryContext); + + if (((flags & MCXT_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(size)) || + ((flags & MCXT_ALLOC_HUGE) == 0 && !AllocSizeIsValid(size))) + elog(ERROR, "invalid memory alloc request size %zu", size); + + CurrentMemoryContext->isReset = false; + + ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size); + if (ret == NULL) + { + 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.", size))); + } + return NULL; + } + + VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size); + + if ((flags & MCXT_ALLOC_ZERO) != 0) + MemSetAligned(ret, 0, size); + + return ret; +} + /* * pfree * Release an allocated chunk. @@ -693,26 +949,7 @@ palloc0(Size size) void pfree(void *pointer) { - MemoryContext context; - - /* - * 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)); - - if (pointer == NULL) - return; - - /* - * OK, it's probably safe to look at the chunk header. - */ - context = ((StandardChunkHeader *) - ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context; - - AssertArg(MemoryContextIsValid(context)); + MemoryContext context = GetMemoryChunkContext(pointer); (*context->methods->free_p) (context, pointer); VALGRIND_MEMPOOL_FREE(context, pointer); @@ -729,31 +966,29 @@ repalloc(void *pointer, Size size) void *ret; if (!AllocSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); - /* pgpool hack by usama */ - if(pointer == NULL) - return palloc(size); - /* - * 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)); + elog(ERROR, "invalid memory alloc request size %zu", size); - /* - * OK, it's probably safe to look at the chunk header. - */ - context = ((StandardChunkHeader *) - ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context; + /* pgpool hack by Muhammad Usama <m.usama@gmail.com> */ + if(pointer == NULL) + return palloc(size); - AssertArg(MemoryContextIsValid(context)); + context = GetMemoryChunkContext(pointer); + + AssertNotInCriticalSection(context); /* isReset must be false already */ Assert(!context->isReset); ret = (*context->methods->realloc) (context, pointer, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size); return ret; @@ -771,14 +1006,23 @@ MemoryContextAllocHuge(MemoryContext context, Size size) void *ret; AssertArg(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); if (!AllocHugeSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); + elog(ERROR, "invalid memory alloc request size %zu", size); context->isReset = false; ret = (*context->methods->alloc) (context, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_ALLOC(context, ret, size); return ret; @@ -792,33 +1036,27 @@ MemoryContextAllocHuge(MemoryContext context, Size size) void * repalloc_huge(void *pointer, Size size) { - MemoryContext context; + MemoryContext context = GetMemoryChunkContext(pointer); void *ret; if (!AllocHugeSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); - - /* - * 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. - */ - context = ((StandardChunkHeader *) - ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context; + elog(ERROR, "invalid memory alloc request size %zu", size); - AssertArg(MemoryContextIsValid(context)); + AssertNotInCriticalSection(context); /* isReset must be false already */ Assert(!context->isReset); ret = (*context->methods->realloc) (context, pointer, size); + if (ret == NULL) + { + MemoryContextStats(TopMemoryContext); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed on request of size %zu.", size))); + } + VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size); return ret; @@ -861,3 +1099,17 @@ pnstrdup(const char *in, Size len) out[len] = '\0'; return out; } + +/* + * Make copy of string with all trailing newline characters removed. + */ +char * +pchomp(const char *in) +{ + size_t n; + + n = strlen(in); + while (n > 0 && in[n - 1] == '\n') + n--; + return pnstrdup(in, n); +} [parent: 2191a8fbc29e]