diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 3bbcb1c894..ea23ac2ccb 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -1110,12 +1110,114 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) set->header.name, chunk); #endif + if (oldsize > set->allocChunkLimit) + { + /* + * The chunk must have been allocated as a single-chunk block. Use + * realloc() to make the containing block bigger, or smaller, with + * minimum space wastage. + */ + AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ); + Size chksize; + Size blksize; + Size oldblksize; + + /* + * 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) + + (oldsize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ)) + elog(ERROR, "could not find block containing chunk %p", chunk); + + /* + * Even if the new request is less than set->allocChunkLimit, we stick + * with the single-chunk block approach. Therefore we need + * chunk->size to be bigger than set->allocChunkLimit, so we don't get + * confused about the chunk's status in future calls. + */ + chksize = Max(size, set->allocChunkLimit + 1); + chksize = MAXALIGN(chksize); + + /* Do the realloc */ + blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; + oldblksize = block->endptr - ((char *)block); + + block = (AllocBlock) realloc(block, blksize); + if (block == NULL) + { + /* Disallow external access to private part of chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); + return NULL; + } + + context->mem_allocated += blksize - oldblksize; + + 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 (block->prev) + block->prev->next = block; + else + set->blocks = block; + if (block->next) + block->next->prev = 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 */ + if (size > chunk->requested_size) + randomize_mem((char *) pointer + chunk->requested_size, + size - chunk->requested_size); +#endif + + /* + * realloc() (or randomize_mem()) will have left any newly-allocated + * part UNDEFINED, but we may need to adjust trailing bytes from the + * old allocation. + */ +#ifdef USE_VALGRIND + if (oldsize > chunk->requested_size) + VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + chunk->requested_size, + oldsize - chunk->requested_size); +#endif + + chunk->requested_size = size; + + /* set mark to catch clobber of "unused" space */ + if (size < chunk->size) + set_sentinel(pointer, size); +#else /* !MEMORY_CONTEXT_CHECKING */ + + /* + * We don't know how much of the old chunk size was the actual + * allocation; it could have been as small as one byte. We have to be + * conservative and just mark the entire old portion DEFINED. + */ + VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize); +#endif + + /* Ensure any padding bytes are marked NOACCESS. */ + VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - size); + + /* Disallow external access to private part of chunk header. */ + VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); + + return pointer; + } + /* - * Chunk sizes are aligned to power of 2 in AllocSetAlloc(). Maybe the - * allocated area already is >= the new size. (In particular, we always + * Chunk sizes are aligned to power of 2 in AllocSetAlloc(). Maybe the + * allocated area already is >= the new size. (In particular, we will * fall out here if the requested size is a decrease.) */ - if (oldsize >= size) + else if (oldsize >= size) { #ifdef MEMORY_CONTEXT_CHECKING Size oldrequest = chunk->requested_size; @@ -1159,105 +1261,15 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) return pointer; } - - if (oldsize > set->allocChunkLimit) - { - /* - * 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 = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ); - Size chksize; - Size blksize; - Size oldblksize; - - /* - * 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); - - /* Do the realloc */ - chksize = MAXALIGN(size); - blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ; - oldblksize = block->endptr - ((char *)block); - - block = (AllocBlock) realloc(block, blksize); - if (block == NULL) - { - /* Disallow external access to private part of chunk header. */ - VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); - return NULL; - } - - context->mem_allocated += blksize - oldblksize; - - 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 (block->prev) - block->prev->next = block; - else - set->blocks = block; - if (block->next) - block->next->prev = 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 *) pointer + chunk->requested_size, - size - chunk->requested_size); -#endif - - /* - * realloc() (or randomize_mem()) will have left the newly-allocated - * part UNDEFINED, but we may need to adjust trailing bytes from the - * old allocation. - */ - VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + chunk->requested_size, - oldsize - chunk->requested_size); - - chunk->requested_size = size; - - /* set mark to catch clobber of "unused" space */ - if (size < chunk->size) - set_sentinel(pointer, size); -#else /* !MEMORY_CONTEXT_CHECKING */ - - /* - * We don't know how much of the old chunk size was the actual - * allocation; it could have been as small as one byte. We have to be - * conservative and just mark the entire old portion DEFINED. - */ - VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize); -#endif - - /* Ensure any padding bytes are marked NOACCESS. */ - VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - size); - - /* Disallow external access to private part of chunk header. */ - VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOCCHUNK_PRIVATE_LEN); - - return pointer; - } 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 + * Enlarge-a-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.)