| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /*------------------------------------------------------------------------- |
| * |
| * aset.c |
| * Allocation set definitions. |
| * |
| * AllocSet is our standard implementation of the abstract MemoryContext |
| * type. |
| * |
| * |
| * Portions Copyright (c) 2007-2008, Greenplum inc |
| * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/backend/utils/mmgr/aset.c,v 1.69 2006/11/08 19:27:24 tgl Exp $ |
| * |
| * NOTE: |
| * This is a new (Feb. 05, 1999) implementation of the allocation set |
| * routines. AllocSet...() does not use OrderedSet...() any more. |
| * Instead it manages allocations in a block pool by itself, combining |
| * many small allocations in a few bigger blocks. AllocSetFree() normally |
| * doesn't free() memory really. It just add's the free'd area to some |
| * list for later reuse by AllocSetAlloc(). All memory blocks are free()'d |
| * at once on AllocSetReset(), which happens when the memory context gets |
| * destroyed. |
| * Jan Wieck |
| * |
| * Performance improvement from Tom Lane, 8/99: for extremely large request |
| * sizes, we do want to be able to give the memory back to free() as soon |
| * as it is pfree()'d. Otherwise we risk tying up a lot of memory in |
| * freelist entries that might never be usable. This is specially needed |
| * when the caller is repeatedly repalloc()'ing a block bigger and bigger; |
| * the previous instances of the block were guaranteed to be wasted until |
| * AllocSetReset() under the old way. |
| * |
| * Further improvement 12/00: as the code stood, request sizes in the |
| * midrange between "small" and "large" were handled very inefficiently, |
| * because any sufficiently large free chunk would be used to satisfy a |
| * 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 |
| * 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. |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include "utils/memutils.h" |
| #include "utils/memaccounting.h" |
| |
| #include "miscadmin.h" |
| |
| /* Define this to detail debug alloc information */ |
| /* #define HAVE_ALLOCINFO */ |
| |
| #ifdef CDB_PALLOC_CALLER_ID |
| #define CDB_MCXT_WHERE(context) (context)->callerFile, (context)->callerLine |
| #else |
| #define CDB_MCXT_WHERE(context) __FILE__, __LINE__ |
| #endif |
| |
| #if defined(CDB_PALLOC_TAGS) && !defined(CDB_PALLOC_CALLER_ID) |
| #error "If CDB_PALLOC_TAGS is defined, CDB_PALLOC_CALLER_ID must be defined too" |
| #endif |
| |
| /*-------------------- |
| * The first block allocated for an allocset has size initBlockSize. |
| * Each time we have to allocate another block, we double the block size |
| * (if possible, and without exceeding maxBlockSize), so as to reduce |
| * the bookkeeping load on malloc(). |
| * |
| * Blocks allocated to hold oversize chunks do not follow this rule, however; |
| * they are just however big they need to be to hold that single chunk. |
| *-------------------- |
| */ |
| |
| #define ALLOC_BLOCKHDRSZ MAXALIGN(sizeof(AllocBlockData)) |
| #define ALLOC_CHUNKHDRSZ MAXALIGN(sizeof(AllocChunkData)) |
| |
| /* |
| * AllocPointer |
| * Aligned pointer which may be a member of an allocation set. |
| */ |
| typedef void *AllocPointer; |
| |
| /* |
| * AllocBlock |
| * An AllocBlock is the unit of memory that is obtained by aset.c |
| * 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 |
| * a matching request size. |
| * |
| * AllocBlockData is the header data for a block --- the usable space |
| * within the block begins at the next alignment boundary. |
| */ |
| typedef struct AllocBlockData |
| { |
| AllocSet aset; /* aset that owns this block */ |
| AllocBlock next; /* next block in aset's blocks list */ |
| char *freeptr; /* start of free space in this block */ |
| char *endptr; /* end of space in this block */ |
| } 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 |
| { |
| /* |
| * SharedChunkHeader stores all the "shared" details |
| * among multiple chunks, such as memoryAccount to charge, |
| * generation of memory account, memory context that owns this |
| * chunk etc. However, in case of a free chunk, this pointer |
| * actually refers to the next chunk in the free list. |
| */ |
| struct SharedChunkHeader* sharedHeader; |
| |
| Size size; /* size of data space allocated in chunk */ |
| |
| /* |
| * The "requested size" of the chunk. This is the intended allocation |
| * size of the client. Though we may end up allocating a larger block |
| * because of AllocSet overhead, optimistic reuse of chunks |
| * and alignment of chunk size at the power of 2 |
| */ |
| #ifdef MEMORY_CONTEXT_CHECKING |
| /* when debugging memory usage, also store actual requested size */ |
| /* this is zero in a free chunk */ |
| Size requested_size; |
| #endif |
| #ifdef CDB_PALLOC_TAGS |
| const char *alloc_tag; |
| int alloc_n; |
| void *prev_chunk; |
| void *next_chunk; |
| #endif |
| } AllocChunkData; |
| |
| /* |
| * AllocPointerIsValid |
| * True iff pointer is valid allocation pointer. |
| */ |
| #define AllocPointerIsValid(pointer) PointerIsValid(pointer) |
| |
| /* |
| * AllocSetIsValid |
| * True iff set is valid allocation set. |
| */ |
| #define AllocSetIsValid(set) PointerIsValid(set) |
| |
| #define AllocPointerGetChunk(ptr) \ |
| ((AllocChunk)(((char *)(ptr)) - ALLOC_CHUNKHDRSZ)) |
| #define AllocChunkGetPointer(chk) \ |
| ((AllocPointer)(((char *)(chk)) + ALLOC_CHUNKHDRSZ)) |
| |
| /* |
| * These functions implement the MemoryContext API for AllocSet contexts. |
| */ |
| static void *AllocSetAlloc(MemoryContext context, Size size); |
| static void *AllocSetAllocHeader(MemoryContext context, Size size); |
| static void AllocSetFree(MemoryContext context, void *pointer); |
| static void AllocSetFreeHeader(MemoryContext context, void *pointer); |
| static void *AllocSetRealloc(MemoryContext context, void *pointer, Size size); |
| static void AllocSetInit(MemoryContext context); |
| static void AllocSetReset(MemoryContext context); |
| static void AllocSetDelete(MemoryContext context); |
| static Size AllocSetGetChunkSpace(MemoryContext context, void *pointer); |
| static bool AllocSetIsEmpty(MemoryContext context); |
| static void AllocSet_GetStats(MemoryContext context, uint64 *nBlocks, uint64 *nChunks, |
| uint64 *currentAvailable, uint64 *allAllocated, uint64 *allFreed, uint64 *maxHeld); |
| static void AllocSetReleaseAccountingForAllAllocatedChunks(MemoryContext context); |
| static void AllocSetUpdateGenerationForAllAllocatedChunks(MemoryContext context); |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| static void AllocSetCheck(MemoryContext context); |
| #endif |
| |
| /* |
| * This is the virtual function table for AllocSet contexts. |
| */ |
| static MemoryContextMethods AllocSetMethods = { |
| AllocSetAlloc, |
| AllocSetFree, |
| AllocSetRealloc, |
| AllocSetInit, |
| AllocSetReset, |
| AllocSetDelete, |
| AllocSetGetChunkSpace, |
| AllocSetIsEmpty, |
| AllocSet_GetStats, |
| AllocSetReleaseAccountingForAllAllocatedChunks, |
| AllocSetUpdateGenerationForAllAllocatedChunks |
| #ifdef MEMORY_CONTEXT_CHECKING |
| ,AllocSetCheck |
| #endif |
| }; |
| |
| |
| /* ---------- |
| * Debug macros |
| * ---------- |
| */ |
| #ifdef CDB_PALLOC_TAGS |
| |
| void dump_memory_allocation(const char* fname) |
| { |
| FILE *ofile = fopen(fname, "w+"); |
| dump_memory_allocation_ctxt(ofile, TopMemoryContext); |
| fclose(ofile); |
| } |
| |
| void dump_memory_allocation_ctxt(FILE *ofile, void *ctxt) |
| { |
| AllocSet set = (AllocSet) ctxt; |
| AllocSet next; |
| AllocChunk chunk = set->allocList; |
| |
| while(chunk) |
| { |
| #ifdef MEMORY_CONTEXT_CHECKING |
| fprintf(ofile, "%ld|%s|%d|%d|%d\n", (long) ctxt, chunk->alloc_tag, chunk->alloc_n, (int) chunk->size, (int) chunk->requested_size); |
| #else |
| fprintf(ofile, "%ld|%s|%d|%d\n", (long) ctxt, chunk->alloc_tag, chunk->alloc_n, (int) chunk->size); |
| #endif |
| chunk = chunk->next_chunk; |
| } |
| |
| next = (AllocSet) set->header.firstchild; |
| while(next) |
| { |
| dump_memory_allocation_ctxt(ofile, next); |
| next = (AllocSet) next->header.nextchild; |
| } |
| } |
| #endif |
| |
| inline void |
| AllocFreeInfo(AllocSet set, AllocChunk chunk, bool isHeader) __attribute__((always_inline)); |
| |
| inline void |
| AllocAllocInfo(AllocSet set, AllocChunk chunk, bool isHeader) __attribute__((always_inline)); |
| |
| inline bool |
| MemoryAccounting_Allocate(struct MemoryAccount* memoryAccount, struct MemoryContextData *context, |
| Size allocatedSize) __attribute__((always_inline)); |
| |
| inline bool |
| MemoryAccounting_Free(struct MemoryAccount* memoryAccount, uint16 memoryAccountGeneration, struct MemoryContextData *context, |
| Size allocatedSize) __attribute__((always_inline)); |
| |
| /* |
| * MemoryAccounting_Allocate |
| * When an allocation is made, this function will be called by the |
| * underlying allocator to record allocation request. |
| * |
| * memoryAccount: where to record this allocation |
| * context: the context where this memory belongs |
| * allocatedSize: the final amount of memory returned by the allocator (with overhead) |
| * |
| * If the return value is false, the underlying memory allocator should fail. |
| */ |
| bool |
| MemoryAccounting_Allocate(struct MemoryAccount* memoryAccount, |
| struct MemoryContextData *context, Size allocatedSize) |
| { |
| Assert(memoryAccount->allocated + allocatedSize >= |
| memoryAccount->allocated); |
| |
| memoryAccount->allocated += allocatedSize; |
| |
| Size held = memoryAccount->allocated - |
| memoryAccount->freed; |
| |
| memoryAccount->peak = |
| Max(memoryAccount->peak, held); |
| |
| Assert(memoryAccount->allocated >= |
| memoryAccount->freed); |
| |
| MemoryAccountingOutstandingBalance += allocatedSize; |
| MemoryAccountingPeakBalance = Max(MemoryAccountingPeakBalance, MemoryAccountingOutstandingBalance); |
| |
| return true; |
| } |
| |
| /* |
| * MemoryAccounting_Free |
| * "One" implementation of free request handler. Each memory account |
| * can customize its free request function. When memory is deallocated, |
| * this function will be called by the underlying allocator to record deallocation. |
| * This function records the amount of memory freed. |
| * |
| * memoryAccount: where to record this allocation |
| * context: the context where this memory belongs |
| * allocatedSize: the final amount of memory returned by the allocator (with overhead) |
| * |
| * Note: the memoryAccount can be an invalid pointer if the generation of |
| * the allocation is different than the current generation. In such case |
| * this method would automatically select RolloverMemoryAccount, instead |
| * of accessing an invalid pointer. |
| */ |
| bool |
| MemoryAccounting_Free(MemoryAccount* memoryAccount, uint16 memoryAccountGeneration, struct MemoryContextData *context, Size allocatedSize) |
| { |
| if (memoryAccountGeneration != MemoryAccountingCurrentGeneration) |
| { |
| memoryAccount = RolloverMemoryAccount; |
| } |
| |
| Assert(MemoryAccountIsValid(memoryAccount)); |
| |
| /* |
| * SharedChunkHeadersMemoryAccount is generation independent, as it is a long |
| * living account. |
| */ |
| Assert(memoryAccount != SharedChunkHeadersMemoryAccount || |
| memoryAccountGeneration == MemoryAccountingCurrentGeneration); |
| |
| Assert(memoryAccount->freed + |
| allocatedSize >= memoryAccount->freed); |
| |
| Assert(memoryAccount->allocated >= memoryAccount->freed); |
| |
| memoryAccount->freed += allocatedSize; |
| |
| MemoryAccountingOutstandingBalance -= allocatedSize; |
| |
| Assert(MemoryAccountingOutstandingBalance >= 0); |
| |
| return true; |
| } |
| |
| /* |
| * AllocFreeInfo |
| * Internal function to remove a chunk from the "used" chunks list. |
| * Also, updates the memory accounting of the chunk. |
| * |
| * set: allocation set that the chunk is part of |
| * chunk: the chunk that is being freed |
| * isHeader: whether the chunk was hosting a shared header for some chunks |
| */ |
| void |
| AllocFreeInfo(AllocSet set, AllocChunk chunk, bool isHeader) |
| { |
| #ifdef MEMORY_CONTEXT_CHECKING |
| Assert(chunk->requested_size != 0xFFFFFFFF); /* This chunk must be in-use. */ |
| #endif |
| |
| /* A header chunk should never have any sharedHeader */ |
| Assert((isHeader && chunk->sharedHeader == NULL) || (!isHeader && chunk->sharedHeader != NULL)); |
| |
| if (!isHeader) |
| { |
| chunk->sharedHeader->balance -= (chunk->size + ALLOC_CHUNKHDRSZ); |
| |
| Assert(chunk->sharedHeader->balance >= 0); |
| |
| /* |
| * Some chunks don't use memory accounting. E.g., any chunks allocated before |
| * memory accounting is setup will get NULL memoryAccount. |
| * Chunks without memory account do not need any accounting adjustment. |
| */ |
| if (chunk->sharedHeader->memoryAccount != NULL) |
| { |
| MemoryAccounting_Free(chunk->sharedHeader->memoryAccount, |
| chunk->sharedHeader->memoryAccountGeneration, (MemoryContext)set, chunk->size + ALLOC_CHUNKHDRSZ); |
| |
| if (chunk->sharedHeader->balance == 0) |
| { |
| /* No chunk is sharing this header, so remove it from the sharedHeaderList */ |
| Assert(set->sharedHeaderList != NULL && |
| (set->sharedHeaderList->next != NULL || set->sharedHeaderList == chunk->sharedHeader)); |
| SharedChunkHeader *prevSharedHeader = chunk->sharedHeader->prev; |
| SharedChunkHeader *nextSharedHeader = chunk->sharedHeader->next; |
| |
| if (prevSharedHeader != NULL) |
| { |
| prevSharedHeader->next = chunk->sharedHeader->next; |
| } |
| else |
| { |
| Assert(set->sharedHeaderList == chunk->sharedHeader); |
| set->sharedHeaderList = nextSharedHeader; |
| } |
| |
| if (nextSharedHeader != NULL) |
| { |
| nextSharedHeader->prev = prevSharedHeader; |
| } |
| |
| /* Free the memory held by the header */ |
| AllocSetFreeHeader((MemoryContext) set, chunk->sharedHeader); |
| } |
| } |
| else |
| { |
| /* |
| * nullAccountHeader assertion. Note: we have already released the shared header balance. |
| * Also note: we don't try to free nullAccountHeader, even if the balance reaches 0 (MPP-22566). |
| */ |
| |
| Assert(chunk->sharedHeader == set->nullAccountHeader); |
| } |
| } |
| else |
| { |
| /* |
| * At this point, we have already freed the chunks that were using this |
| * SharedChunkHeader and the chunk's shared header had a memory account |
| * (otherwise we don't call AllocSetFreeHeader()). So, the header should |
| * reduce the balance of SharedChunkHeadersMemoryAccount. Note, if we |
| * decide to fix MPP-22566 (the nullAccountHeader releasing), we need |
| * to check if we are releasing nullAccountHeader, as that header is not |
| * charged against SharedChunkMemoryAccount, and that result in a |
| * negative balance for SharedChunkMemoryAccount. |
| */ |
| MemoryAccounting_Free(SharedChunkHeadersMemoryAccount, |
| MemoryAccountingCurrentGeneration, (MemoryContext)set, chunk->size + ALLOC_CHUNKHDRSZ); |
| } |
| |
| #ifdef CDB_PALLOC_TAGS |
| AllocChunk prev = chunk->prev_chunk; |
| AllocChunk next = chunk->next_chunk; |
| |
| if(prev != NULL) |
| { |
| prev->next_chunk = next; |
| } |
| else |
| { |
| Assert(set->allocList == chunk); |
| set->allocList = next; |
| } |
| |
| if(next != NULL) |
| { |
| next->prev_chunk = prev; |
| } |
| #endif |
| } |
| |
| /* |
| * AllocAllocInfo |
| * Internal function to add a "newly used" (may be already allocated) chunk |
| * into the "used" chunks list. |
| * Also, updates the memory accounting of the chunk. |
| * |
| * set: allocation set that the chunk is part of |
| * chunk: the chunk that is being freed |
| * isHeader: whether the chunk will be used to host a shared header for another chunk |
| */ |
| void |
| AllocAllocInfo(AllocSet set, AllocChunk chunk, bool isHeader) |
| { |
| if (!isHeader) |
| { |
| /* |
| * We only start tallying memory after the initial setup is done. |
| * We may not keep accounting for some chunk's memory: e.g., TopMemoryContext |
| * or MemoryAccountMemoryContext gets allocated even before we start assigning |
| * accounts to any chunks. |
| */ |
| if (ActiveMemoryAccount != NULL) |
| { |
| Assert(MemoryAccountIsValid(ActiveMemoryAccount)); |
| |
| SharedChunkHeader *desiredHeader = set->sharedHeaderList; |
| |
| /* Try to look-ahead in the sharedHeaderList to find the desiredHeader */ |
| if (set->sharedHeaderList != NULL && set->sharedHeaderList->memoryAccount == ActiveMemoryAccount && |
| set->sharedHeaderList->memoryAccountGeneration == MemoryAccountingCurrentGeneration) |
| { |
| /* Do nothing, we already assigned sharedHeaderList to desiredHeader */ |
| } |
| else if (set->sharedHeaderList != NULL && set->sharedHeaderList->next != NULL && |
| set->sharedHeaderList->next->memoryAccount == ActiveMemoryAccount && |
| set->sharedHeaderList->next->memoryAccountGeneration == MemoryAccountingCurrentGeneration) |
| { |
| desiredHeader = set->sharedHeaderList->next; |
| } |
| else if (set->sharedHeaderList != NULL && set->sharedHeaderList->next != NULL && |
| set->sharedHeaderList->next->next != NULL && |
| set->sharedHeaderList->next->next->memoryAccount == ActiveMemoryAccount && |
| set->sharedHeaderList->next->next->memoryAccountGeneration == MemoryAccountingCurrentGeneration) |
| { |
| desiredHeader = set->sharedHeaderList->next->next; |
| } |
| else |
| { |
| /* The last 3 headers are not suitable for next chunk, so we need a new shared header */ |
| |
| desiredHeader = AllocSetAllocHeader((MemoryContext) set, sizeof(SharedChunkHeader)); |
| |
| desiredHeader->context = (MemoryContext) set; |
| desiredHeader->memoryAccount = ActiveMemoryAccount; |
| desiredHeader->memoryAccountGeneration = MemoryAccountingCurrentGeneration; |
| desiredHeader->balance = 0; |
| |
| desiredHeader->next = set->sharedHeaderList; |
| if (desiredHeader->next != NULL) |
| { |
| desiredHeader->next->prev = desiredHeader; |
| } |
| desiredHeader->prev = NULL; |
| |
| set->sharedHeaderList = desiredHeader; |
| } |
| |
| desiredHeader->balance += (chunk->size + ALLOC_CHUNKHDRSZ); |
| chunk->sharedHeader = desiredHeader; |
| |
| MemoryAccounting_Allocate(ActiveMemoryAccount, |
| (MemoryContext)set, chunk->size + ALLOC_CHUNKHDRSZ); |
| } |
| else |
| { |
| /* We have NULL ActiveMemoryAccount, so use nullAccountHeader */ |
| |
| if (set->nullAccountHeader == NULL) |
| { |
| /* |
| * SharedChunkHeadersMemoryAccount comes to life first. So, if |
| * ActiveMemoryAccount is NULL, so should be the SharedChunkHeadersMemoryAccount |
| */ |
| Assert(ActiveMemoryAccount == NULL && SharedChunkHeadersMemoryAccount == NULL); |
| |
| /* We initialize nullAccountHeader only if necessary */ |
| SharedChunkHeader *desiredHeader = AllocSetAllocHeader((MemoryContext) set, sizeof(SharedChunkHeader)); |
| desiredHeader->context = (MemoryContext) set; |
| desiredHeader->memoryAccount = NULL; |
| desiredHeader->memoryAccountGeneration = MemoryAccountingCurrentGeneration; |
| desiredHeader->balance = 0; |
| |
| set->nullAccountHeader = desiredHeader; |
| |
| /* |
| * No need to charge SharedChunkHeadersMemoryAccount for |
| * the nullAccountHeader as a null ActiveMemoryAccount |
| * automatically implies a null SharedChunkHeadersMemoryAccount |
| */ |
| } |
| |
| chunk->sharedHeader = set->nullAccountHeader; |
| set->nullAccountHeader->balance += (chunk->size + ALLOC_CHUNKHDRSZ); |
| } |
| } |
| else |
| { |
| /* |
| * At this point we still may have NULL SharedChunksHeadersMemoryAccount. |
| * Note: this is only possible if the ActiveMemoryAccount and |
| * SharedChunksHeadersMemoryAccount both are null, and we are |
| * trying to create a nullAccountHeader |
| */ |
| if (SharedChunkHeadersMemoryAccount != NULL) |
| { |
| MemoryAccounting_Allocate(SharedChunkHeadersMemoryAccount, |
| (MemoryContext)set, chunk->size + ALLOC_CHUNKHDRSZ); |
| } |
| |
| /* |
| * The only reason a sharedChunkHeader can be NULL is the chunk |
| * is allocated (not part of the freelist), and it is being used |
| * to store shared header for other chunks |
| */ |
| chunk->sharedHeader = NULL; |
| } |
| |
| #ifdef CDB_PALLOC_TAGS |
| chunk->prev_chunk = NULL; |
| |
| /* |
| * We are not double-calling AllocAllocInfo where chunk is the only chunk in the allocList. |
| * Note: the double call is only detectable if allocList is currently pointing to this chunk |
| * (i.e., this chunk is the head). To detect generic case, we need another flag, which we |
| * are avoiding here. Also note, if chunk is the head, then it will create a circular linked |
| * list, otherwise we might just corrupt the linked list |
| */ |
| Assert(!chunk->next_chunk || chunk != set->allocList); |
| |
| chunk->next_chunk = set->allocList; |
| |
| if(set->allocList) |
| { |
| set->allocList->prev_chunk = chunk; |
| } |
| |
| set->allocList = chunk; |
| |
| chunk->alloc_tag = set->header.callerFile; |
| chunk->alloc_n = set->header.callerLine; |
| #endif |
| } |
| |
| /* ---------- |
| * AllocSetFreeIndex - |
| * |
| * Depending on the size of an allocation compute which freechunk |
| * list of the alloc set it belongs to. Caller must have verified |
| * that size <= ALLOC_CHUNK_LIMIT. |
| * ---------- |
| */ |
| static inline int |
| AllocSetFreeIndex(Size size) |
| { |
| int idx = 0; |
| |
| if (size > 0) |
| { |
| size = (size - 1) >> ALLOC_MINBITS; |
| while (size != 0) |
| { |
| idx++; |
| size >>= 1; |
| } |
| Assert(idx < ALLOCSET_NUM_FREELISTS); |
| } |
| |
| return idx; |
| } |
| |
| #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. |
| */ |
| static void |
| randomize_mem(char *ptr, size_t size) |
| { |
| static int save_ctr = 1; |
| int ctr; |
| |
| ctr = save_ctr; |
| while (size-- > 0) |
| { |
| *ptr++ = ctr; |
| if (++ctr > 251) |
| ctr = 1; |
| } |
| save_ctr = ctr; |
| } |
| |
| #endif /* RANDOMIZE_ALLOCATED_MEMORY */ |
| |
| |
| /* |
| * Public routines |
| */ |
| |
| |
| /* |
| * AllocSetContextCreate |
| * Create a new AllocSet context. |
| * |
| * parent: parent context, or NULL if top-level context |
| * name: name of context (for debugging --- string will be copied) |
| * minContextSize: minimum context size |
| * initBlockSize: initial allocation block size |
| * maxBlockSize: maximum allocation block size |
| */ |
| MemoryContext |
| AllocSetContextCreate(MemoryContext parent, |
| const char *name, |
| Size minContextSize, |
| Size initBlockSize, |
| Size maxBlockSize) |
| { |
| AllocSet context; |
| |
| /* Do the type-independent part of context creation */ |
| context = (AllocSet) MemoryContextCreate(T_AllocSetContext, |
| sizeof(AllocSetContext), |
| &AllocSetMethods, |
| parent, |
| name); |
| |
| /* |
| * Make sure alloc parameters are reasonable, and save them. |
| * |
| * 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; |
| context->initBlockSize = initBlockSize; |
| context->maxBlockSize = maxBlockSize; |
| context->nextBlockSize = initBlockSize; |
| |
| context->sharedHeaderList = NULL; |
| |
| #ifdef CDB_PALLOC_TAGS |
| context->allocList = NULL; |
| #endif |
| |
| /* |
| * Grab always-allocated space, if requested |
| */ |
| if (minContextSize > ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ) |
| { |
| Size blksize = MAXALIGN(minContextSize); |
| AllocBlock block; |
| |
| block = (AllocBlock) gp_malloc(blksize); |
| if (block == NULL) |
| MemoryContextError(ERRCODE_OUT_OF_MEMORY, |
| &context->header, CDB_MCXT_WHERE(&context->header), |
| "Out of memory. Unable to allocate %lu bytes.", |
| (unsigned long)blksize); |
| block->aset = context; |
| block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; |
| block->endptr = ((char *) block) + blksize; |
| block->next = context->blocks; |
| context->blocks = block; |
| /* Mark block as not to be released at reset time */ |
| context->keeper = block; |
| |
| MemoryContextNoteAlloc(&context->header, blksize); /*CDB*/ |
| /* |
| * We are allocating new memory in this block, but we are not accounting |
| * for this. The concept of memory accounting is to track the actual |
| * allocation/deallocation by the memory user. This block is preemptively |
| * allocating memory, which is "unused" by actual consumers. Therefore, |
| * memory accounting currently wouldn't track this |
| */ |
| } |
| |
| context->isReset = true; |
| |
| context->nullAccountHeader = NULL; |
| |
| return (MemoryContext) context; |
| } |
| |
| /* |
| * AllocSetInit |
| * Context-type-specific initialization routine. |
| * |
| * This is called by MemoryContextCreate() after setting up the |
| * generic MemoryContext fields and before linking the new context |
| * into the context tree. We must do whatever is needed to make the |
| * new context minimally valid for deletion. We must *not* risk |
| * failure --- thus, for example, allocating more memory is not cool. |
| * (AllocSetContextCreate can allocate memory when it gets control |
| * back, however.) |
| */ |
| static void |
| AllocSetInit(MemoryContext context) |
| { |
| /* |
| * Since MemoryContextCreate already zeroed the context node, we don't |
| * have to do anything here: it's already OK. |
| */ |
| } |
| |
| |
| /* |
| * AllocSetReleaseAccountingForAllAllocatedChunks |
| * Iterates through all the shared headers in the sharedHeaderList |
| * and release their accounting information using the correct MemoryAccount. |
| * |
| * This is called by AllocSetReset() or AllocSetDelete(). In other words, any time we |
| * bulk release all the chunks that are in-use, we want to update the corresponding |
| * accounting information. |
| * |
| * This is also part of the function pointers of MemoryContextMethods. During the |
| * memory accounting reset, this is called to release all the chunk accounting |
| * in MemoryAccountMemoryContext without actually deletion the chunks. |
| * |
| * This method can be called multiple times during a memory context reset process |
| * without any harm. It correctly removes all the shared headers from the sharedHeaderList |
| * on the first use. So, on subsequent use we do not "double" free the memory accounts. |
| */ |
| static void AllocSetReleaseAccountingForAllAllocatedChunks(MemoryContext context) |
| { |
| AllocSet set = (AllocSet) context; |
| |
| /* The memory consumed by the shared headers themselves */ |
| uint64 sharedHeaderMemoryOverhead = 0; |
| |
| for (SharedChunkHeader* curHeader = set->sharedHeaderList; curHeader != NULL; |
| curHeader = curHeader->next) |
| { |
| Assert(curHeader->balance > 0); |
| MemoryAccounting_Free(curHeader->memoryAccount, |
| curHeader->memoryAccountGeneration, context, curHeader->balance); |
| |
| AllocChunk chunk = AllocPointerGetChunk(curHeader); |
| |
| sharedHeaderMemoryOverhead += (chunk->size + ALLOC_CHUNKHDRSZ); |
| } |
| |
| /* |
| * In addition to releasing accounting for the chunks, we also need |
| * to release accounting for the shared headers |
| */ |
| MemoryAccounting_Free(SharedChunkHeadersMemoryAccount, |
| MemoryAccountingCurrentGeneration, context, sharedHeaderMemoryOverhead); |
| |
| /* |
| * Wipe off the sharedHeaderList. We don't free any memory here, |
| * as this method is only supposed to be called during reset |
| */ |
| set->sharedHeaderList = NULL; |
| |
| #ifdef CDB_PALLOC_TAGS |
| set->allocList = NULL; |
| #endif |
| } |
| |
| /* |
| * AllocSetUpdateGenerationForAllAllocatedChunks |
| * Iterates through all the shared headers and updates their memory accounting |
| * generation. During this process, all the headers' accounts are set to RolloverMemoryAccount. |
| * |
| * Parameters: |
| * context: The context for which to update the generation |
| */ |
| static void AllocSetUpdateGenerationForAllAllocatedChunks(MemoryContext context) |
| { |
| AllocSet set = (AllocSet) context; |
| |
| for (SharedChunkHeader* curHeader = set->sharedHeaderList; curHeader != NULL; |
| curHeader = curHeader->next) |
| { |
| curHeader->memoryAccount = RolloverMemoryAccount; |
| curHeader->memoryAccountGeneration = MemoryAccountingCurrentGeneration; |
| } |
| } |
| |
| /* |
| * AllocSetReset |
| * Frees all memory which is allocated in the given set. |
| * |
| * 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, |
| * we don't thrash malloc() when a context is repeatedly reset after small |
| * allocations, which is typical behavior for per-tuple contexts. |
| */ |
| static void |
| AllocSetReset(MemoryContext context) |
| { |
| AllocSet set = (AllocSet) context; |
| AllocBlock block; |
| |
| AssertArg(AllocSetIsValid(set)); |
| |
| /* Nothing to do if no pallocs since startup or last reset */ |
| if (set->isReset) |
| return; |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| /* Check for corruption and leaks before freeing */ |
| AllocSetCheck(context); |
| #endif |
| |
| /* Before we wipe off the allocList, we must ensure that the MemoryAccounts |
| * who holds the allocation accounting for the chunks now release these |
| * allocation accounting. |
| */ |
| AllocSetReleaseAccountingForAllAllocatedChunks(context); |
| |
| /* Clear chunk freelists */ |
| MemSetAligned(set->freelist, 0, sizeof(set->freelist)); |
| |
| block = set->blocks; |
| |
| /* New blocks list is either empty or just the keeper block */ |
| set->blocks = set->keeper; |
| |
| while (block != NULL) |
| { |
| AllocBlock next = block->next; |
| |
| if (block == set->keeper) |
| { |
| /* Reset the block, but don't return it to malloc */ |
| char *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ; |
| |
| #ifdef CLOBBER_FREED_MEMORY |
| /* Wipe freed memory for debugging purposes */ |
| memset(datastart, 0x7F, block->freeptr - datastart); |
| #endif |
| block->freeptr = datastart; |
| block->next = NULL; |
| } |
| else |
| { |
| size_t freesz = block->endptr - (char *) block; |
| |
| /* Normal case, release the block */ |
| MemoryContextNoteFree(&set->header, freesz); |
| |
| #ifdef CLOBBER_FREED_MEMORY |
| /* Wipe freed memory for debugging purposes */ |
| memset(block, 0x7F, block->freeptr - ((char *) block)); |
| #endif |
| gp_free2(block, freesz); |
| } |
| block = next; |
| } |
| |
| /* Reset block size allocation sequence, too */ |
| set->nextBlockSize = set->initBlockSize; |
| |
| set->isReset = true; |
| |
| set->nullAccountHeader = NULL; |
| } |
| |
| /* |
| * AllocSetDelete |
| * Frees all memory which is allocated in the given set, |
| * in preparation for deletion of the set. |
| * |
| * Unlike AllocSetReset, this *must* free all resources of the set. |
| * But note we are not responsible for deleting the context node itself. |
| */ |
| static void |
| AllocSetDelete(MemoryContext context) |
| { |
| AllocSet set = (AllocSet) context; |
| AllocBlock block = set->blocks; |
| |
| AssertArg(AllocSetIsValid(set)); |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| /* Check for corruption and leaks before freeing */ |
| AllocSetCheck(context); |
| #endif |
| |
| AllocSetReleaseAccountingForAllAllocatedChunks(context); |
| |
| /* Make it look empty, just in case... */ |
| MemSetAligned(set->freelist, 0, sizeof(set->freelist)); |
| set->blocks = NULL; |
| set->keeper = NULL; |
| |
| while (block != NULL) |
| { |
| AllocBlock next = block->next; |
| size_t freesz = block->endptr - (char *) block; |
| MemoryContextNoteFree(&set->header, freesz); |
| |
| #ifdef CLOBBER_FREED_MEMORY |
| /* Wipe freed memory for debugging purposes */ |
| memset(block, 0x7F, block->freeptr - ((char *) block)); |
| #endif |
| gp_free2(block, freesz); |
| block = next; |
| } |
| |
| set->sharedHeaderList = NULL; |
| set->nullAccountHeader = NULL; |
| } |
| |
| /* |
| * AllocSetAllocImpl |
| * Returns pointer to allocated memory of given size; memory is added |
| * to the set. |
| * |
| * Parameters: |
| * context: the context under which the memory was allocated |
| * size: size of the memory to allocate |
| * isHeader: whether the memory will be hosting a shared header |
| * |
| * Returns the pointer to the memory region. |
| */ |
| static void * |
| AllocSetAllocImpl(MemoryContext context, Size size, bool isHeader) |
| { |
| AllocSet set = (AllocSet) context; |
| AllocBlock block; |
| AllocChunk chunk; |
| int fidx; |
| Size chunk_size; |
| Size blksize; |
| |
| AssertArg(AllocSetIsValid(set)); |
| #ifdef USE_ASSERT_CHECKING |
| if (IsUnderPostmaster && context != ErrorContext && mainthread() != 0 && !pthread_equal(main_tid, pthread_self())) |
| { |
| #if defined(__darwin__) |
| elog(ERROR,"palloc called from thread (OS-X pthread_sigmask is broken: MPP-4923)"); |
| #else |
| elog(ERROR,"palloc called from thread"); |
| #endif |
| } |
| #endif |
| |
| /* |
| * If requested size exceeds maximum for chunks, allocate an entire block |
| * for this request. |
| */ |
| if (size > ALLOC_CHUNK_LIMIT) |
| { |
| chunk_size = MAXALIGN(size); |
| blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; |
| block = (AllocBlock) gp_malloc(blksize); |
| if (block == NULL) |
| MemoryContextError(ERRCODE_OUT_OF_MEMORY, |
| &set->header, CDB_MCXT_WHERE(&set->header), |
| "Out of memory. Failed on request of size %lu bytes.", |
| (unsigned long)size); |
| block->aset = set; |
| block->freeptr = block->endptr = ((char *) block) + blksize; |
| |
| chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ); |
| chunk->size = chunk_size; |
| /* We use malloc internally, which may not 0 out the memory. */ |
| chunk->sharedHeader = NULL; |
| /* set mark to catch clobber of "unused" space */ |
| #ifdef MEMORY_CONTEXT_CHECKING |
| chunk->requested_size = size; |
| if (size < chunk_size) |
| { |
| ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; |
| } |
| #endif |
| #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| /* fill the allocated space with junk */ |
| randomize_mem((char *) AllocChunkGetPointer(chunk), size); |
| #endif |
| |
| /* |
| * Stick the new block underneath the active allocation block, so that |
| * we don't lose the use of the space remaining therein. |
| */ |
| if (set->blocks != NULL) |
| { |
| block->next = set->blocks->next; |
| set->blocks->next = block; |
| } |
| else |
| { |
| block->next = NULL; |
| set->blocks = block; |
| } |
| |
| set->isReset = false; |
| MemoryContextNoteAlloc(&set->header, blksize); /*CDB*/ |
| |
| AllocAllocInfo(set, chunk, isHeader); |
| return AllocChunkGetPointer(chunk); |
| } |
| |
| /* |
| * Request is small enough to be treated as a chunk. Look in the |
| * corresponding free list to see if there is a free chunk we could reuse. |
| * If one is found, remove it from the free list, make it again a member |
| * of the alloc set and return its data address. |
| */ |
| fidx = AllocSetFreeIndex(size); |
| chunk = set->freelist[fidx]; |
| if (chunk != NULL) |
| { |
| Assert(chunk->size >= size); |
| |
| set->freelist[fidx] = (AllocChunk) chunk->sharedHeader; |
| |
| /* |
| * The sharedHeader pointer until now was pointing to |
| * the next free chunk in this freelist. As this chunk |
| * is just removed from freelist, it no longer points |
| * to the next free chunk in this freelist |
| */ |
| chunk->sharedHeader = NULL; |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| chunk->requested_size = size; |
| /* set mark to catch clobber of "unused" space */ |
| if (size < chunk->size) |
| { |
| ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; |
| } |
| #endif |
| #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| /* fill the allocated space with junk */ |
| randomize_mem((char *) AllocChunkGetPointer(chunk), size); |
| #endif |
| |
| /* isReset must be false already */ |
| Assert(!set->isReset); |
| |
| AllocAllocInfo(set, chunk, isHeader); |
| return AllocChunkGetPointer(chunk); |
| } |
| |
| /* |
| * Choose the actual chunk size to allocate. |
| */ |
| chunk_size = (1 << ALLOC_MINBITS) << fidx; |
| Assert(chunk_size >= size); |
| |
| /* |
| * If there is enough room in the active allocation block, we will put the |
| * chunk into that block. Else must start a new one. |
| */ |
| if ((block = set->blocks) != NULL) |
| { |
| Size availspace = block->endptr - block->freeptr; |
| |
| if (availspace < (chunk_size + ALLOC_CHUNKHDRSZ)) |
| { |
| /* |
| * The existing active (top) block does not have enough room for |
| * the requested allocation, but it might still have a useful |
| * amount of space in it. Once we push it down in the block list, |
| * we'll never try to allocate more space from it. So, before we |
| * do that, carve up its free space into chunks that we can put on |
| * the set's freelists. |
| * |
| * Because we can only get here when there's less than |
| * ALLOC_CHUNK_LIMIT left in the block, this loop cannot iterate |
| * more than ALLOCSET_NUM_FREELISTS-1 times. |
| */ |
| while (availspace >= ((1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ)) |
| { |
| Size availchunk = availspace - ALLOC_CHUNKHDRSZ; |
| int a_fidx = AllocSetFreeIndex(availchunk); |
| |
| /* |
| * 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 |
| * exception is when availchunk is exactly a power of 2. |
| */ |
| if (availchunk != ((Size)1 << (a_fidx + ALLOC_MINBITS))) |
| { |
| a_fidx--; |
| Assert(a_fidx >= 0); |
| availchunk = ((Size)1 << (a_fidx + ALLOC_MINBITS)); |
| } |
| |
| chunk = (AllocChunk) (block->freeptr); |
| |
| block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ); |
| availspace -= (availchunk + ALLOC_CHUNKHDRSZ); |
| |
| chunk->size = availchunk; |
| #ifdef MEMORY_CONTEXT_CHECKING |
| chunk->requested_size = 0xFFFFFFFF; /* mark it free */ |
| #endif |
| chunk->sharedHeader = (void *) set->freelist[a_fidx]; |
| set->freelist[a_fidx] = chunk; |
| } |
| |
| /* Mark that we need to create a new block */ |
| block = NULL; |
| } |
| } |
| |
| /* |
| * Time to create a new regular (multi-chunk) block? |
| */ |
| if (block == NULL) |
| { |
| Size required_size; |
| |
| /* |
| * The first such block has size initBlockSize, and we double the |
| * space in each succeeding block, but not more than maxBlockSize. |
| */ |
| blksize = set->nextBlockSize; |
| set->nextBlockSize <<= 1; |
| if (set->nextBlockSize > set->maxBlockSize) |
| set->nextBlockSize = set->maxBlockSize; |
| |
| /* |
| * If initBlockSize is less than ALLOC_CHUNK_LIMIT, we could need more |
| * space... but try to keep it a power of 2. |
| */ |
| required_size = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; |
| while (blksize < required_size) |
| blksize <<= 1; |
| |
| /* Try to allocate it */ |
| block = (AllocBlock) gp_malloc(blksize); |
| |
| /* |
| * We could be asking for pretty big blocks here, so cope if malloc |
| * fails. But give up if there's less than a meg or so available... |
| */ |
| while (block == NULL && blksize > 1024 * 1024) |
| { |
| blksize >>= 1; |
| if (blksize < required_size) |
| break; |
| block = (AllocBlock) gp_malloc(blksize); |
| } |
| |
| if (block == NULL) |
| MemoryContextError(ERRCODE_OUT_OF_MEMORY, |
| &set->header, CDB_MCXT_WHERE(&set->header), |
| "Out of memory. Failed on request of size %lu bytes.", |
| (unsigned long)size); |
| |
| block->aset = set; |
| block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; |
| block->endptr = ((char *) block) + blksize; |
| |
| /* |
| * If this is the first block of the set, make it the "keeper" block. |
| * Formerly, a keeper block could only be created during context |
| * creation, but allowing it to happen here lets us have fast reset |
| * cycling even for contexts created with minContextSize = 0; that way |
| * we don't have to force space to be allocated in contexts that might |
| * never need any space. Don't mark an oversize block as a keeper, |
| * however. |
| */ |
| if (set->keeper == NULL && blksize == set->initBlockSize) |
| set->keeper = block; |
| |
| block->next = set->blocks; |
| set->blocks = block; |
| MemoryContextNoteAlloc(&set->header, blksize); /*CDB*/ |
| } |
| |
| /* |
| * OK, do the allocation |
| */ |
| chunk = (AllocChunk) (block->freeptr); |
| |
| block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ); |
| Assert(block->freeptr <= block->endptr); |
| |
| chunk->sharedHeader = NULL; |
| chunk->size = chunk_size; |
| #ifdef MEMORY_CONTEXT_CHECKING |
| chunk->requested_size = size; |
| /* set mark to catch clobber of "unused" space */ |
| if (size < chunk->size) |
| { |
| ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; |
| } |
| #endif |
| #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| /* fill the allocated space with junk */ |
| randomize_mem((char *) AllocChunkGetPointer(chunk), size); |
| #endif |
| |
| set->isReset = false; |
| |
| AllocAllocInfo(set, chunk, isHeader); |
| |
| return AllocChunkGetPointer(chunk); |
| } |
| |
| |
| /* |
| * AllocSetAlloc |
| * Returns pointer to an allocated memory of given size; memory is added |
| * to the set. |
| */ |
| static void * |
| AllocSetAlloc(MemoryContext context, Size size) |
| { |
| return AllocSetAllocImpl(context, size, false); |
| } |
| |
| /* |
| * AllocSetAllocHeader |
| * Returns pointer to an allocated memory of a given size |
| * that will be used to host a shared header for other chunks |
| */ |
| static void * |
| AllocSetAllocHeader(MemoryContext context, Size size) |
| { |
| return AllocSetAllocImpl(context, size, true); |
| } |
| |
| /* |
| * AllocSetFreeImpl |
| * Frees allocated memory; memory is removed from the set. |
| * |
| * Parameters: |
| * context: the context under which the memory was allocated |
| * pointer: the pointer to free |
| * isHeader: whether the memory was hosting a shared header |
| */ |
| static void |
| AllocSetFreeImpl(MemoryContext context, void *pointer, bool isHeader) |
| { |
| AllocSet set = (AllocSet) context; |
| AllocChunk chunk = AllocPointerGetChunk(pointer); |
| |
| Assert(chunk->size > 0); |
| AllocFreeInfo(set, chunk, isHeader); |
| |
| #ifdef USE_ASSERT_CHECKING |
| /* |
| * This check doesnt work because pfree is called during error handling from inside |
| * AtAbort_Portals. That can only happen if the error was from a thread (say a SEGV) |
| */ |
| /* |
| if (IsUnderPostmaster && context != ErrorContext && mainthread() != 0 && !pthread_equal(main_tid, pthread_self())) |
| { |
| elog(ERROR,"pfree called from thread"); |
| } |
| */ |
| #endif |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| Assert(chunk->requested_size != 0xFFFFFFFF); |
| /* Test for someone scribbling on unused space in chunk */ |
| if (chunk->requested_size < chunk->size) |
| { |
| if (((char *) pointer)[chunk->requested_size] != 0x7E) |
| { |
| Assert(!"Memory error"); |
| elog(WARNING, "detected write past chunk end in %s %p (%s:%d)", |
| set->header.name, chunk, CDB_MCXT_WHERE(&set->header)); |
| } |
| } |
| #endif |
| |
| if (chunk->size > ALLOC_CHUNK_LIMIT) |
| { |
| /* |
| * Big chunks are certain to have been allocated as single-chunk |
| * blocks. Find the containing block and return it to malloc(). |
| */ |
| AllocBlock block = set->blocks; |
| AllocBlock prevblock = NULL; |
| |
| size_t freesz; |
| |
| while (block != NULL) |
| { |
| if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ)) |
| break; |
| prevblock = block; |
| block = block->next; |
| } |
| if (block == NULL) |
| MemoryContextError(ERRCODE_INTERNAL_ERROR, |
| &set->header, CDB_MCXT_WHERE(&set->header), |
| "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; |
| else |
| prevblock->next = block->next; |
| |
| freesz = block->endptr - (char *) block; |
| MemoryContextNoteFree(&set->header, freesz); |
| gp_free2(block, freesz); |
| } |
| else |
| { |
| /* Normal case, put the chunk into appropriate freelist */ |
| int fidx = AllocSetFreeIndex(chunk->size); |
| |
| chunk->sharedHeader = (void *) set->freelist[fidx]; |
| |
| #ifdef CLOBBER_FREED_MEMORY |
| /* Wipe freed memory for debugging purposes */ |
| memset(pointer, 0x7F, chunk->size); |
| #endif |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| /* Reset requested_size to 0 in chunks that are on freelist */ |
| chunk->requested_size = 0xFFFFFFFF; |
| #endif |
| set->freelist[fidx] = chunk; |
| Assert(chunk->size > 0); |
| } |
| } |
| |
| /* |
| * AllocSetFree |
| * Frees allocated memory; memory is removed from the set. |
| */ |
| static void |
| AllocSetFree(MemoryContext context, void *pointer) |
| { |
| AllocSetFreeImpl(context, pointer, false); |
| } |
| |
| /* |
| * AllocSetFreeHeader |
| * Frees an allocated memory that was hosting a shared header |
| * for other chunks |
| */ |
| static void |
| AllocSetFreeHeader(MemoryContext context, void *pointer) |
| { |
| AllocSetFreeImpl(context, pointer, true); |
| } |
| /* |
| * 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. |
| */ |
| static void * |
| AllocSetRealloc(MemoryContext context, void *pointer, Size size) |
| { |
| AllocSet set = (AllocSet) context; |
| AllocChunk chunk = AllocPointerGetChunk(pointer); |
| Size oldsize = chunk->size; |
| |
| #ifdef USE_ASSERT_CHECKING |
| if (IsUnderPostmaster && context != ErrorContext && mainthread() != 0 && !pthread_equal(main_tid, pthread_self())) |
| { |
| #if defined(__darwin__) |
| elog(ERROR,"prealloc called from thread (OS-X pthread_sigmask is broken: MPP-4923)"); |
| #else |
| elog(ERROR,"prealloc called from thread"); |
| #endif |
| } |
| #endif |
| #ifdef MEMORY_CONTEXT_CHECKING |
| Assert(chunk->requested_size != 0xFFFFFFFF); |
| /* Test for someone scribbling on unused space in chunk */ |
| if (chunk->requested_size < oldsize) |
| { |
| if (((char *) pointer)[chunk->requested_size] != 0x7E) |
| { |
| Assert(!"Memory error"); |
| elog(WARNING, "detected write past chunk end in %s %p (%s:%d)", |
| set->header.name, chunk, CDB_MCXT_WHERE(&set->header)); |
| } |
| } |
| #endif |
| |
| /* isReset must be false already */ |
| Assert(!set->isReset); |
| |
| /* |
| * Chunk sizes are aligned to power of 2 in AllocSetAlloc(). Maybe the |
| * allocated area already is >= the new size. (In particular, we always |
| * fall out here if the requested size is a decrease.) |
| */ |
| if (oldsize >= size) |
| { |
| /* isHeader is set to false as we should never require realloc for shared header */ |
| AllocFreeInfo(set, chunk, false); |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| /* We can only fill the extra space if we know the prior request */ |
| if (size > chunk->requested_size) |
| { |
| randomize_mem((char *) AllocChunkGetPointer(chunk) + chunk->requested_size, |
| size - chunk->requested_size); |
| } |
| #endif |
| |
| chunk->requested_size = size; |
| /* set mark to catch clobber of "unused" space */ |
| if (size < oldsize) |
| { |
| ((char *) pointer)[size] = 0x7E; |
| } |
| #endif |
| |
| /* isHeader is set to false as we should never require realloc for shared header */ |
| AllocAllocInfo(set, chunk, false); |
| |
| /* |
| * Note: we do not adjust the freeptr of the block. This would mess up any "SUPER-SIZED" |
| * block, as we do not want the "SUPER-SIZED" block as active block and end up allocating |
| * additional chunk from that. |
| */ |
| return pointer; |
| } |
| |
| if (oldsize > ALLOC_CHUNK_LIMIT) |
| { |
| /* |
| * 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. |
| */ |
| AllocBlock block = set->blocks; |
| AllocBlock prevblock = NULL; |
| Size chksize; |
| Size blksize; |
| Size oldblksize; |
| |
| /* isHeader is set to false as we should never require realloc for shared header */ |
| AllocFreeInfo(set, chunk, false); |
| |
| while (block != NULL) |
| { |
| if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ)) |
| break; |
| prevblock = block; |
| block = block->next; |
| } |
| if (block == NULL) |
| MemoryContextError(ERRCODE_INTERNAL_ERROR, |
| &set->header, CDB_MCXT_WHERE(&set->header), |
| "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 */ |
| oldblksize = block->endptr - (char *)block; |
| chksize = MAXALIGN(size); |
| blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; |
| block = (AllocBlock) gp_realloc(block, oldblksize, blksize); |
| if (block == NULL) |
| MemoryContextError(ERRCODE_OUT_OF_MEMORY, |
| &set->header, CDB_MCXT_WHERE(&set->header), |
| "Out of memory. Failed on request of size %lu bytes.", |
| (unsigned long)size); |
| block->freeptr = block->endptr = ((char *) block) + blksize; |
| |
| /* Update pointers since block has likely been moved */ |
| chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ); |
| if (prevblock == NULL) |
| set->blocks = block; |
| else |
| prevblock->next = block; |
| chunk->size = chksize; |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| /* We can only fill the extra space if we know the prior request */ |
| randomize_mem((char *) AllocChunkGetPointer(chunk) + chunk->requested_size, |
| size - chunk->requested_size); |
| #endif |
| |
| chunk->requested_size = size; |
| /* set mark to catch clobber of "unused" space */ |
| if (size < chunk->size) |
| ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; |
| #endif |
| |
| AllocAllocInfo(set, chunk, false /* We should never require realloc for shared header */); |
| |
| MemoryContextNoteAlloc(&set->header, blksize - oldblksize); /*CDB*/ |
| return AllocChunkGetPointer(chunk); |
| } |
| else |
| { |
| /* |
| * Small-chunk case. We just do this by brute force, ie, allocate a |
| * new chunk and copy the data. Since we know the existing data isn't |
| * huge, this won't involve any great memcpy expense, so it's not |
| * worth being smarter. (At one time we tried to avoid memcpy when it |
| * was possible to enlarge the chunk in-place, but that turns out to |
| * misbehave unpleasantly for repeated cycles of |
| * palloc/repalloc/pfree: the eventually freed chunks go into the |
| * wrong freelist for the next initial palloc request, and so we leak |
| * memory indefinitely. See pgsql-hackers archives for 2007-08-11.) |
| */ |
| AllocPointer newPointer; |
| |
| /* |
| * We do not call AllocAllocInfo() or AllocFreeInfo() in this case. |
| * The corresponding AllocSetAlloc() and AllocSetFree() take care |
| * of updating the memory accounting. |
| */ |
| |
| /* allocate new chunk */ |
| newPointer = AllocSetAlloc((MemoryContext) set, size); |
| |
| /* transfer existing data (certain to fit) */ |
| memcpy(newPointer, pointer, oldsize); |
| |
| /* free old chunk */ |
| AllocSetFree((MemoryContext) set, pointer); |
| |
| return newPointer; |
| } |
| } |
| |
| /* |
| * AllocSetGetChunkSpace |
| * Given a currently-allocated chunk, determine the total space |
| * it occupies (including all memory-allocation overhead). |
| */ |
| static Size |
| AllocSetGetChunkSpace(MemoryContext context, void *pointer) |
| { |
| AllocChunk chunk = AllocPointerGetChunk(pointer); |
| |
| return chunk->size + ALLOC_CHUNKHDRSZ; |
| } |
| |
| /* |
| * AllocSetIsEmpty |
| * Is an allocset empty of any allocated space? |
| */ |
| static bool |
| AllocSetIsEmpty(MemoryContext context) |
| { |
| AllocSet set = (AllocSet) context; |
| |
| /* |
| * For now, we say "empty" only if the context is new or just reset. We |
| * could examine the freelists to determine if all space has been freed, |
| * but it's not really worth the trouble for present uses of this |
| * functionality. |
| */ |
| if (set->isReset) |
| return true; |
| return false; |
| } |
| |
| /* |
| * AllocSet_GetStats |
| * Returns stats about memory consumption of an AllocSet. |
| * |
| * Input parameters: |
| * context: the context of interest |
| * |
| * Output parameters: |
| * nBlocks: number of blocks in the context |
| * nChunks: number of chunks in the context |
| * |
| * currentAvailable: free space across all blocks |
| * |
| * allAllocated: total bytes allocated during lifetime (including |
| * blocks that was dropped later on, e.g., freeing a large chunk |
| * in an exclusive block would drop the block) |
| * |
| * allFreed: total bytes that was freed during lifetime |
| * maxHeld: maximum bytes held during lifetime |
| */ |
| static void |
| AllocSet_GetStats(MemoryContext context, uint64 *nBlocks, uint64 *nChunks, |
| uint64 *currentAvailable, uint64 *allAllocated, uint64 *allFreed, uint64 *maxHeld) |
| { |
| AllocSet set = (AllocSet) context; |
| AllocBlock block; |
| AllocChunk chunk; |
| int fidx; |
| uint64 currentAllocated = 0; |
| |
| *nBlocks = 0; |
| *nChunks = 0; |
| *currentAvailable = 0; |
| *allAllocated = set->header.allBytesAlloc; |
| *allFreed = set->header.allBytesFreed; |
| *maxHeld = set->header.maxBytesHeld; |
| |
| /* Total space obtained from host's memory manager */ |
| for (block = set->blocks; block != NULL; block = block->next) |
| { |
| *nBlocks = *nBlocks + 1; |
| currentAllocated += block->endptr - ((char *) block); |
| } |
| |
| /* Space at end of first block is available for use. */ |
| if (set->blocks) |
| { |
| *nChunks = *nChunks + 1; |
| *currentAvailable += set->blocks->endptr - set->blocks->freeptr; |
| } |
| |
| /* Freelists. Count usable space only, not chunk headers. */ |
| for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++) |
| { |
| for (chunk = set->freelist[fidx]; chunk != NULL; |
| chunk = (AllocChunk) chunk->sharedHeader) |
| { |
| *nChunks = *nChunks + 1; |
| *currentAvailable += chunk->size; |
| } |
| } |
| } |
| |
| #ifdef MEMORY_CONTEXT_CHECKING |
| |
| /* |
| * AllocSetCheck |
| * Walk through chunks and check consistency of memory. |
| * |
| * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll |
| * find yourself in an infinite loop when trouble occurs, because this |
| * routine will be entered again when elog cleanup tries to release memory! |
| */ |
| static void |
| AllocSetCheck(MemoryContext context) |
| { |
| AllocSet set = (AllocSet) context; |
| char *name = set->header.name; |
| AllocBlock block; |
| |
| for (block = set->blocks; block != NULL; block = block->next) |
| { |
| char *bpoz = ((char *) block) + ALLOC_BLOCKHDRSZ; |
| Size blk_used = block->freeptr - bpoz; |
| Size blk_data = 0; |
| long nchunks = 0; |
| |
| /* |
| * Empty block - empty can be keeper-block only |
| */ |
| if (!blk_used) |
| { |
| if (set->keeper != block) |
| { |
| Assert(!"Memory Context problem"); |
| elog(WARNING, "problem in alloc set %s: empty block %p (%s:%d)", |
| name, block, CDB_MCXT_WHERE(&set->header)); |
| } |
| } |
| |
| /* |
| * Chunk walker |
| */ |
| while (bpoz < block->freeptr) |
| { |
| AllocChunk chunk = (AllocChunk) bpoz; |
| Size chsize, |
| dsize; |
| char *chdata_end; |
| |
| chsize = chunk->size; /* aligned chunk size */ |
| dsize = chunk->requested_size; /* real data */ |
| chdata_end = ((char *) chunk) + (ALLOC_CHUNKHDRSZ + dsize); |
| |
| /* |
| * Check chunk size |
| */ |
| if (dsize != 0xFFFFFFFF && dsize > chsize) |
| { |
| Assert(!"Memory Context error"); |
| elog(WARNING, "problem in alloc set %s: req size > alloc size for chunk %p in block %p (%s:%d)", |
| name, chunk, block, CDB_MCXT_WHERE(&set->header)); |
| } |
| |
| if (chsize < (1 << ALLOC_MINBITS)) |
| { |
| Assert(!"Memory Context Error"); |
| elog(WARNING, "problem in alloc set %s: bad size %lu for chunk %p in block %p (%s:%d)", |
| name, (unsigned long) chsize, chunk, block, CDB_MCXT_WHERE(&set->header)); |
| } |
| |
| /* single-chunk block? */ |
| if (chsize > ALLOC_CHUNK_LIMIT && chsize + ALLOC_CHUNKHDRSZ != blk_used) |
| { |
| Assert(!"Memory context error"); |
| elog(WARNING, "problem in alloc set %s: bad single-chunk %p in block %p (%s:%d)", |
| name, chunk, block, CDB_MCXT_WHERE(&set->header)); |
| } |
| |
| /* |
| * If chunk is allocated, check for correct aset pointer. (If it's |
| * free, the aset is the freelist pointer, which we can't check as |
| * easily...) |
| */ |
| if (dsize != 0xFFFFFFFF && chunk->sharedHeader != NULL && chunk->sharedHeader->context != (void *) set) |
| { |
| Assert(!"Memory context error"); |
| elog(WARNING, "problem in alloc set %s: bogus aset link in block %p, chunk %p (%s:%d)", |
| name, block, chunk, CDB_MCXT_WHERE(&set->header)); |
| } |
| |
| /* |
| * Check for overwrite of "unallocated" space in chunk |
| */ |
| if (dsize != 0xFFFFFFFF && dsize != 0 |
| && dsize < chsize && *chdata_end != 0x7E) |
| { |
| // Assert(!"Memory context error"); |
| elog(ERROR, "problem in alloc set %s: detected write past chunk end in block %p, chunk %p (%s:%d)", |
| name, block, chunk, CDB_MCXT_WHERE(&set->header)); |
| } |
| |
| blk_data += chsize; |
| nchunks++; |
| |
| bpoz += ALLOC_CHUNKHDRSZ + chsize; |
| } |
| |
| if ((blk_data + (nchunks * ALLOC_CHUNKHDRSZ)) != blk_used) |
| { |
| Assert(!"Memory context error"); |
| elog(WARNING, "problem in alloc set %s: found inconsistent memory block %p (%s:%d)", |
| name, block, CDB_MCXT_WHERE(&set->header)); |
| } |
| } |
| } |
| |
| #endif /* MEMORY_CONTEXT_CHECKING */ |