| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * generation.c |
| 4 | * Generational allocator definitions. |
| 5 | * |
| 6 | * Generation is a custom MemoryContext implementation designed for cases of |
| 7 | * chunks with similar lifespan. |
| 8 | * |
| 9 | * Portions Copyright (c) 2017-2019, PostgreSQL Global Development Group |
| 10 | * |
| 11 | * IDENTIFICATION |
| 12 | * src/backend/utils/mmgr/generation.c |
| 13 | * |
| 14 | * |
| 15 | * This memory context is based on the assumption that the chunks are freed |
| 16 | * roughly in the same order as they were allocated (FIFO), or in groups with |
| 17 | * similar lifespan (generations - hence the name of the context). This is |
| 18 | * typical for various queue-like use cases, i.e. when tuples are constructed, |
| 19 | * processed and then thrown away. |
| 20 | * |
| 21 | * The memory context uses a very simple approach to free space management. |
| 22 | * Instead of a complex global freelist, each block tracks a number |
| 23 | * of allocated and freed chunks. Freed chunks are not reused, and once all |
| 24 | * chunks in a block are freed, the whole block is thrown away. When the |
| 25 | * chunks allocated in the same block have similar lifespan, this works |
| 26 | * very well and is very cheap. |
| 27 | * |
| 28 | * The current implementation only uses a fixed block size - maybe it should |
| 29 | * adapt a min/max block size range, and grow the blocks automatically. |
| 30 | * It already uses dedicated blocks for oversized chunks. |
| 31 | * |
| 32 | * XXX It might be possible to improve this by keeping a small freelist for |
| 33 | * only a small number of recent blocks, but it's not clear it's worth the |
| 34 | * additional complexity. |
| 35 | * |
| 36 | *------------------------------------------------------------------------- |
| 37 | */ |
| 38 | |
| 39 | #include "postgres.h" |
| 40 | |
| 41 | #include "lib/ilist.h" |
| 42 | #include "utils/memdebug.h" |
| 43 | #include "utils/memutils.h" |
| 44 | |
| 45 | |
| 46 | #define Generation_BLOCKHDRSZ MAXALIGN(sizeof(GenerationBlock)) |
| 47 | #define Generation_CHUNKHDRSZ sizeof(GenerationChunk) |
| 48 | |
| 49 | typedef struct GenerationBlock GenerationBlock; /* forward reference */ |
| 50 | typedef struct GenerationChunk GenerationChunk; |
| 51 | |
| 52 | typedef void *GenerationPointer; |
| 53 | |
| 54 | /* |
| 55 | * GenerationContext is a simple memory context not reusing allocated chunks, |
| 56 | * and freeing blocks once all chunks are freed. |
| 57 | */ |
| 58 | typedef struct GenerationContext |
| 59 | { |
| 60 | MemoryContextData ; /* Standard memory-context fields */ |
| 61 | |
| 62 | /* Generational context parameters */ |
| 63 | Size blockSize; /* standard block size */ |
| 64 | |
| 65 | GenerationBlock *block; /* current (most recently allocated) block */ |
| 66 | dlist_head blocks; /* list of blocks */ |
| 67 | } GenerationContext; |
| 68 | |
| 69 | /* |
| 70 | * GenerationBlock |
| 71 | * GenerationBlock is the unit of memory that is obtained by generation.c |
| 72 | * from malloc(). It contains one or more GenerationChunks, which are |
| 73 | * the units requested by palloc() and freed by pfree(). GenerationChunks |
| 74 | * cannot be returned to malloc() individually, instead pfree() |
| 75 | * updates the free counter of the block and when all chunks in a block |
| 76 | * are free the whole block is returned to malloc(). |
| 77 | * |
| 78 | * GenerationBlock is the header data for a block --- the usable space |
| 79 | * within the block begins at the next alignment boundary. |
| 80 | */ |
| 81 | struct GenerationBlock |
| 82 | { |
| 83 | dlist_node node; /* doubly-linked list of blocks */ |
| 84 | Size blksize; /* allocated size of this block */ |
| 85 | int nchunks; /* number of chunks in the block */ |
| 86 | int nfree; /* number of free chunks */ |
| 87 | char *freeptr; /* start of free space in this block */ |
| 88 | char *endptr; /* end of space in this block */ |
| 89 | }; |
| 90 | |
| 91 | /* |
| 92 | * GenerationChunk |
| 93 | * The prefix of each piece of memory in a GenerationBlock |
| 94 | * |
| 95 | * Note: to meet the memory context APIs, the payload area of the chunk must |
| 96 | * be maxaligned, and the "context" link must be immediately adjacent to the |
| 97 | * payload area (cf. GetMemoryChunkContext). We simplify matters for this |
| 98 | * module by requiring sizeof(GenerationChunk) to be maxaligned, and then |
| 99 | * we can ensure things work by adding any required alignment padding before |
| 100 | * the pointer fields. There is a static assertion below that the alignment |
| 101 | * is done correctly. |
| 102 | */ |
| 103 | struct GenerationChunk |
| 104 | { |
| 105 | /* size is always the size of the usable space in the chunk */ |
| 106 | Size size; |
| 107 | #ifdef MEMORY_CONTEXT_CHECKING |
| 108 | /* when debugging memory usage, also store actual requested size */ |
| 109 | /* this is zero in a free chunk */ |
| 110 | Size requested_size; |
| 111 | |
| 112 | #define GENERATIONCHUNK_RAWSIZE (SIZEOF_SIZE_T * 2 + SIZEOF_VOID_P * 2) |
| 113 | #else |
| 114 | #define GENERATIONCHUNK_RAWSIZE (SIZEOF_SIZE_T + SIZEOF_VOID_P * 2) |
| 115 | #endif /* MEMORY_CONTEXT_CHECKING */ |
| 116 | |
| 117 | /* ensure proper alignment by adding padding if needed */ |
| 118 | #if (GENERATIONCHUNK_RAWSIZE % MAXIMUM_ALIGNOF) != 0 |
| 119 | char padding[MAXIMUM_ALIGNOF - GENERATIONCHUNK_RAWSIZE % MAXIMUM_ALIGNOF]; |
| 120 | #endif |
| 121 | |
| 122 | GenerationBlock *block; /* block owning this chunk */ |
| 123 | GenerationContext *context; /* owning context, or NULL if freed chunk */ |
| 124 | /* there must not be any padding to reach a MAXALIGN boundary here! */ |
| 125 | }; |
| 126 | |
| 127 | /* |
| 128 | * Only the "context" field should be accessed outside this module. |
| 129 | * We keep the rest of an allocated chunk's header marked NOACCESS when using |
| 130 | * valgrind. But note that freed chunk headers are kept accessible, for |
| 131 | * simplicity. |
| 132 | */ |
| 133 | #define GENERATIONCHUNK_PRIVATE_LEN offsetof(GenerationChunk, context) |
| 134 | |
| 135 | /* |
| 136 | * GenerationIsValid |
| 137 | * True iff set is valid allocation set. |
| 138 | */ |
| 139 | #define GenerationIsValid(set) PointerIsValid(set) |
| 140 | |
| 141 | #define GenerationPointerGetChunk(ptr) \ |
| 142 | ((GenerationChunk *)(((char *)(ptr)) - Generation_CHUNKHDRSZ)) |
| 143 | #define GenerationChunkGetPointer(chk) \ |
| 144 | ((GenerationPointer *)(((char *)(chk)) + Generation_CHUNKHDRSZ)) |
| 145 | |
| 146 | /* |
| 147 | * These functions implement the MemoryContext API for Generation contexts. |
| 148 | */ |
| 149 | static void *GenerationAlloc(MemoryContext context, Size size); |
| 150 | static void GenerationFree(MemoryContext context, void *pointer); |
| 151 | static void *GenerationRealloc(MemoryContext context, void *pointer, Size size); |
| 152 | static void GenerationReset(MemoryContext context); |
| 153 | static void GenerationDelete(MemoryContext context); |
| 154 | static Size GenerationGetChunkSpace(MemoryContext context, void *pointer); |
| 155 | static bool GenerationIsEmpty(MemoryContext context); |
| 156 | static void GenerationStats(MemoryContext context, |
| 157 | MemoryStatsPrintFunc printfunc, void *passthru, |
| 158 | MemoryContextCounters *totals); |
| 159 | |
| 160 | #ifdef MEMORY_CONTEXT_CHECKING |
| 161 | static void GenerationCheck(MemoryContext context); |
| 162 | #endif |
| 163 | |
| 164 | /* |
| 165 | * This is the virtual function table for Generation contexts. |
| 166 | */ |
| 167 | static const MemoryContextMethods GenerationMethods = { |
| 168 | GenerationAlloc, |
| 169 | GenerationFree, |
| 170 | GenerationRealloc, |
| 171 | GenerationReset, |
| 172 | GenerationDelete, |
| 173 | GenerationGetChunkSpace, |
| 174 | GenerationIsEmpty, |
| 175 | GenerationStats |
| 176 | #ifdef MEMORY_CONTEXT_CHECKING |
| 177 | ,GenerationCheck |
| 178 | #endif |
| 179 | }; |
| 180 | |
| 181 | /* ---------- |
| 182 | * Debug macros |
| 183 | * ---------- |
| 184 | */ |
| 185 | #ifdef HAVE_ALLOCINFO |
| 186 | #define GenerationFreeInfo(_cxt, _chunk) \ |
| 187 | fprintf(stderr, "GenerationFree: %s: %p, %lu\n", \ |
| 188 | (_cxt)->name, (_chunk), (_chunk)->size) |
| 189 | #define GenerationAllocInfo(_cxt, _chunk) \ |
| 190 | fprintf(stderr, "GenerationAlloc: %s: %p, %lu\n", \ |
| 191 | (_cxt)->name, (_chunk), (_chunk)->size) |
| 192 | #else |
| 193 | #define GenerationFreeInfo(_cxt, _chunk) |
| 194 | #define GenerationAllocInfo(_cxt, _chunk) |
| 195 | #endif |
| 196 | |
| 197 | |
| 198 | /* |
| 199 | * Public routines |
| 200 | */ |
| 201 | |
| 202 | |
| 203 | /* |
| 204 | * GenerationContextCreate |
| 205 | * Create a new Generation context. |
| 206 | * |
| 207 | * parent: parent context, or NULL if top-level context |
| 208 | * name: name of context (must be statically allocated) |
| 209 | * blockSize: generation block size |
| 210 | */ |
| 211 | MemoryContext |
| 212 | GenerationContextCreate(MemoryContext parent, |
| 213 | const char *name, |
| 214 | Size blockSize) |
| 215 | { |
| 216 | GenerationContext *set; |
| 217 | |
| 218 | /* Assert we padded GenerationChunk properly */ |
| 219 | StaticAssertStmt(Generation_CHUNKHDRSZ == MAXALIGN(Generation_CHUNKHDRSZ), |
| 220 | "sizeof(GenerationChunk) is not maxaligned" ); |
| 221 | StaticAssertStmt(offsetof(GenerationChunk, context) + sizeof(MemoryContext) == |
| 222 | Generation_CHUNKHDRSZ, |
| 223 | "padding calculation in GenerationChunk is wrong" ); |
| 224 | |
| 225 | /* |
| 226 | * First, validate allocation parameters. (If we're going to throw an |
| 227 | * error, we should do so before the context is created, not after.) We |
| 228 | * somewhat arbitrarily enforce a minimum 1K block size, mostly because |
| 229 | * that's what AllocSet does. |
| 230 | */ |
| 231 | if (blockSize != MAXALIGN(blockSize) || |
| 232 | blockSize < 1024 || |
| 233 | !AllocHugeSizeIsValid(blockSize)) |
| 234 | elog(ERROR, "invalid blockSize for memory context: %zu" , |
| 235 | blockSize); |
| 236 | |
| 237 | /* |
| 238 | * Allocate the context header. Unlike aset.c, we never try to combine |
| 239 | * this with the first regular block, since that would prevent us from |
| 240 | * freeing the first generation of allocations. |
| 241 | */ |
| 242 | |
| 243 | set = (GenerationContext *) malloc(MAXALIGN(sizeof(GenerationContext))); |
| 244 | if (set == NULL) |
| 245 | { |
| 246 | MemoryContextStats(TopMemoryContext); |
| 247 | ereport(ERROR, |
| 248 | (errcode(ERRCODE_OUT_OF_MEMORY), |
| 249 | errmsg("out of memory" ), |
| 250 | errdetail("Failed while creating memory context \"%s\"." , |
| 251 | name))); |
| 252 | } |
| 253 | |
| 254 | /* |
| 255 | * Avoid writing code that can fail between here and MemoryContextCreate; |
| 256 | * we'd leak the header if we ereport in this stretch. |
| 257 | */ |
| 258 | |
| 259 | /* Fill in GenerationContext-specific header fields */ |
| 260 | set->blockSize = blockSize; |
| 261 | set->block = NULL; |
| 262 | dlist_init(&set->blocks); |
| 263 | |
| 264 | /* Finally, do the type-independent part of context creation */ |
| 265 | MemoryContextCreate((MemoryContext) set, |
| 266 | T_GenerationContext, |
| 267 | &GenerationMethods, |
| 268 | parent, |
| 269 | name); |
| 270 | |
| 271 | return (MemoryContext) set; |
| 272 | } |
| 273 | |
| 274 | /* |
| 275 | * GenerationReset |
| 276 | * Frees all memory which is allocated in the given set. |
| 277 | * |
| 278 | * The code simply frees all the blocks in the context - we don't keep any |
| 279 | * keeper blocks or anything like that. |
| 280 | */ |
| 281 | static void |
| 282 | GenerationReset(MemoryContext context) |
| 283 | { |
| 284 | GenerationContext *set = (GenerationContext *) context; |
| 285 | dlist_mutable_iter miter; |
| 286 | |
| 287 | AssertArg(GenerationIsValid(set)); |
| 288 | |
| 289 | #ifdef MEMORY_CONTEXT_CHECKING |
| 290 | /* Check for corruption and leaks before freeing */ |
| 291 | GenerationCheck(context); |
| 292 | #endif |
| 293 | |
| 294 | dlist_foreach_modify(miter, &set->blocks) |
| 295 | { |
| 296 | GenerationBlock *block = dlist_container(GenerationBlock, node, miter.cur); |
| 297 | |
| 298 | dlist_delete(miter.cur); |
| 299 | |
| 300 | #ifdef CLOBBER_FREED_MEMORY |
| 301 | wipe_mem(block, block->blksize); |
| 302 | #endif |
| 303 | |
| 304 | free(block); |
| 305 | } |
| 306 | |
| 307 | set->block = NULL; |
| 308 | |
| 309 | Assert(dlist_is_empty(&set->blocks)); |
| 310 | } |
| 311 | |
| 312 | /* |
| 313 | * GenerationDelete |
| 314 | * Free all memory which is allocated in the given context. |
| 315 | */ |
| 316 | static void |
| 317 | GenerationDelete(MemoryContext context) |
| 318 | { |
| 319 | /* Reset to release all the GenerationBlocks */ |
| 320 | GenerationReset(context); |
| 321 | /* And free the context header */ |
| 322 | free(context); |
| 323 | } |
| 324 | |
| 325 | /* |
| 326 | * GenerationAlloc |
| 327 | * Returns pointer to allocated memory of given size or NULL if |
| 328 | * request could not be completed; memory is added to the set. |
| 329 | * |
| 330 | * No request may exceed: |
| 331 | * MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ |
| 332 | * All callers use a much-lower limit. |
| 333 | * |
| 334 | * Note: when using valgrind, it doesn't matter how the returned allocation |
| 335 | * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will |
| 336 | * return space that is marked NOACCESS - GenerationRealloc has to beware! |
| 337 | */ |
| 338 | static void * |
| 339 | GenerationAlloc(MemoryContext context, Size size) |
| 340 | { |
| 341 | GenerationContext *set = (GenerationContext *) context; |
| 342 | GenerationBlock *block; |
| 343 | GenerationChunk *chunk; |
| 344 | Size chunk_size = MAXALIGN(size); |
| 345 | |
| 346 | /* is it an over-sized chunk? if yes, allocate special block */ |
| 347 | if (chunk_size > set->blockSize / 8) |
| 348 | { |
| 349 | Size blksize = chunk_size + Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ; |
| 350 | |
| 351 | block = (GenerationBlock *) malloc(blksize); |
| 352 | if (block == NULL) |
| 353 | return NULL; |
| 354 | |
| 355 | /* block with a single (used) chunk */ |
| 356 | block->blksize = blksize; |
| 357 | block->nchunks = 1; |
| 358 | block->nfree = 0; |
| 359 | |
| 360 | /* the block is completely full */ |
| 361 | block->freeptr = block->endptr = ((char *) block) + blksize; |
| 362 | |
| 363 | chunk = (GenerationChunk *) (((char *) block) + Generation_BLOCKHDRSZ); |
| 364 | chunk->block = block; |
| 365 | chunk->context = set; |
| 366 | chunk->size = chunk_size; |
| 367 | |
| 368 | #ifdef MEMORY_CONTEXT_CHECKING |
| 369 | chunk->requested_size = size; |
| 370 | /* set mark to catch clobber of "unused" space */ |
| 371 | if (size < chunk_size) |
| 372 | set_sentinel(GenerationChunkGetPointer(chunk), size); |
| 373 | #endif |
| 374 | #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| 375 | /* fill the allocated space with junk */ |
| 376 | randomize_mem((char *) GenerationChunkGetPointer(chunk), size); |
| 377 | #endif |
| 378 | |
| 379 | /* add the block to the list of allocated blocks */ |
| 380 | dlist_push_head(&set->blocks, &block->node); |
| 381 | |
| 382 | GenerationAllocInfo(set, chunk); |
| 383 | |
| 384 | /* Ensure any padding bytes are marked NOACCESS. */ |
| 385 | VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(chunk) + size, |
| 386 | chunk_size - size); |
| 387 | |
| 388 | /* Disallow external access to private part of chunk header. */ |
| 389 | VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 390 | |
| 391 | return GenerationChunkGetPointer(chunk); |
| 392 | } |
| 393 | |
| 394 | /* |
| 395 | * Not an over-sized chunk. Is there enough space in the current block? If |
| 396 | * not, allocate a new "regular" block. |
| 397 | */ |
| 398 | block = set->block; |
| 399 | |
| 400 | if ((block == NULL) || |
| 401 | (block->endptr - block->freeptr) < Generation_CHUNKHDRSZ + chunk_size) |
| 402 | { |
| 403 | Size blksize = set->blockSize; |
| 404 | |
| 405 | block = (GenerationBlock *) malloc(blksize); |
| 406 | |
| 407 | if (block == NULL) |
| 408 | return NULL; |
| 409 | |
| 410 | block->blksize = blksize; |
| 411 | block->nchunks = 0; |
| 412 | block->nfree = 0; |
| 413 | |
| 414 | block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ; |
| 415 | block->endptr = ((char *) block) + blksize; |
| 416 | |
| 417 | /* Mark unallocated space NOACCESS. */ |
| 418 | VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, |
| 419 | blksize - Generation_BLOCKHDRSZ); |
| 420 | |
| 421 | /* add it to the doubly-linked list of blocks */ |
| 422 | dlist_push_head(&set->blocks, &block->node); |
| 423 | |
| 424 | /* and also use it as the current allocation block */ |
| 425 | set->block = block; |
| 426 | } |
| 427 | |
| 428 | /* we're supposed to have a block with enough free space now */ |
| 429 | Assert(block != NULL); |
| 430 | Assert((block->endptr - block->freeptr) >= Generation_CHUNKHDRSZ + chunk_size); |
| 431 | |
| 432 | chunk = (GenerationChunk *) block->freeptr; |
| 433 | |
| 434 | /* Prepare to initialize the chunk header. */ |
| 435 | VALGRIND_MAKE_MEM_UNDEFINED(chunk, Generation_CHUNKHDRSZ); |
| 436 | |
| 437 | block->nchunks += 1; |
| 438 | block->freeptr += (Generation_CHUNKHDRSZ + chunk_size); |
| 439 | |
| 440 | Assert(block->freeptr <= block->endptr); |
| 441 | |
| 442 | chunk->block = block; |
| 443 | chunk->context = set; |
| 444 | chunk->size = chunk_size; |
| 445 | |
| 446 | #ifdef MEMORY_CONTEXT_CHECKING |
| 447 | chunk->requested_size = size; |
| 448 | /* set mark to catch clobber of "unused" space */ |
| 449 | if (size < chunk->size) |
| 450 | set_sentinel(GenerationChunkGetPointer(chunk), size); |
| 451 | #endif |
| 452 | #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| 453 | /* fill the allocated space with junk */ |
| 454 | randomize_mem((char *) GenerationChunkGetPointer(chunk), size); |
| 455 | #endif |
| 456 | |
| 457 | GenerationAllocInfo(set, chunk); |
| 458 | |
| 459 | /* Ensure any padding bytes are marked NOACCESS. */ |
| 460 | VALGRIND_MAKE_MEM_NOACCESS((char *) GenerationChunkGetPointer(chunk) + size, |
| 461 | chunk_size - size); |
| 462 | |
| 463 | /* Disallow external access to private part of chunk header. */ |
| 464 | VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 465 | |
| 466 | return GenerationChunkGetPointer(chunk); |
| 467 | } |
| 468 | |
| 469 | /* |
| 470 | * GenerationFree |
| 471 | * Update number of chunks in the block, and if all chunks in the block |
| 472 | * are now free then discard the block. |
| 473 | */ |
| 474 | static void |
| 475 | GenerationFree(MemoryContext context, void *pointer) |
| 476 | { |
| 477 | GenerationContext *set = (GenerationContext *) context; |
| 478 | GenerationChunk *chunk = GenerationPointerGetChunk(pointer); |
| 479 | GenerationBlock *block; |
| 480 | |
| 481 | /* Allow access to private part of chunk header. */ |
| 482 | VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 483 | |
| 484 | block = chunk->block; |
| 485 | |
| 486 | #ifdef MEMORY_CONTEXT_CHECKING |
| 487 | /* Test for someone scribbling on unused space in chunk */ |
| 488 | if (chunk->requested_size < chunk->size) |
| 489 | if (!sentinel_ok(pointer, chunk->requested_size)) |
| 490 | elog(WARNING, "detected write past chunk end in %s %p" , |
| 491 | ((MemoryContext) set)->name, chunk); |
| 492 | #endif |
| 493 | |
| 494 | #ifdef CLOBBER_FREED_MEMORY |
| 495 | wipe_mem(pointer, chunk->size); |
| 496 | #endif |
| 497 | |
| 498 | /* Reset context to NULL in freed chunks */ |
| 499 | chunk->context = NULL; |
| 500 | |
| 501 | #ifdef MEMORY_CONTEXT_CHECKING |
| 502 | /* Reset requested_size to 0 in freed chunks */ |
| 503 | chunk->requested_size = 0; |
| 504 | #endif |
| 505 | |
| 506 | block->nfree += 1; |
| 507 | |
| 508 | Assert(block->nchunks > 0); |
| 509 | Assert(block->nfree <= block->nchunks); |
| 510 | |
| 511 | /* If there are still allocated chunks in the block, we're done. */ |
| 512 | if (block->nfree < block->nchunks) |
| 513 | return; |
| 514 | |
| 515 | /* |
| 516 | * The block is empty, so let's get rid of it. First remove it from the |
| 517 | * list of blocks, then return it to malloc(). |
| 518 | */ |
| 519 | dlist_delete(&block->node); |
| 520 | |
| 521 | /* Also make sure the block is not marked as the current block. */ |
| 522 | if (set->block == block) |
| 523 | set->block = NULL; |
| 524 | |
| 525 | free(block); |
| 526 | } |
| 527 | |
| 528 | /* |
| 529 | * GenerationRealloc |
| 530 | * When handling repalloc, we simply allocate a new chunk, copy the data |
| 531 | * and discard the old one. The only exception is when the new size fits |
| 532 | * into the old chunk - in that case we just update chunk header. |
| 533 | */ |
| 534 | static void * |
| 535 | GenerationRealloc(MemoryContext context, void *pointer, Size size) |
| 536 | { |
| 537 | GenerationContext *set = (GenerationContext *) context; |
| 538 | GenerationChunk *chunk = GenerationPointerGetChunk(pointer); |
| 539 | GenerationPointer newPointer; |
| 540 | Size oldsize; |
| 541 | |
| 542 | /* Allow access to private part of chunk header. */ |
| 543 | VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 544 | |
| 545 | oldsize = chunk->size; |
| 546 | |
| 547 | #ifdef MEMORY_CONTEXT_CHECKING |
| 548 | /* Test for someone scribbling on unused space in chunk */ |
| 549 | if (chunk->requested_size < oldsize) |
| 550 | if (!sentinel_ok(pointer, chunk->requested_size)) |
| 551 | elog(WARNING, "detected write past chunk end in %s %p" , |
| 552 | ((MemoryContext) set)->name, chunk); |
| 553 | #endif |
| 554 | |
| 555 | /* |
| 556 | * Maybe the allocated area already is >= the new size. (In particular, |
| 557 | * we always fall out here if the requested size is a decrease.) |
| 558 | * |
| 559 | * This memory context does not use power-of-2 chunk sizing and instead |
| 560 | * carves the chunks to be as small as possible, so most repalloc() calls |
| 561 | * will end up in the palloc/memcpy/pfree branch. |
| 562 | * |
| 563 | * XXX Perhaps we should annotate this condition with unlikely()? |
| 564 | */ |
| 565 | if (oldsize >= size) |
| 566 | { |
| 567 | #ifdef MEMORY_CONTEXT_CHECKING |
| 568 | Size oldrequest = chunk->requested_size; |
| 569 | |
| 570 | #ifdef RANDOMIZE_ALLOCATED_MEMORY |
| 571 | /* We can only fill the extra space if we know the prior request */ |
| 572 | if (size > oldrequest) |
| 573 | randomize_mem((char *) pointer + oldrequest, |
| 574 | size - oldrequest); |
| 575 | #endif |
| 576 | |
| 577 | chunk->requested_size = size; |
| 578 | |
| 579 | /* |
| 580 | * If this is an increase, mark any newly-available part UNDEFINED. |
| 581 | * Otherwise, mark the obsolete part NOACCESS. |
| 582 | */ |
| 583 | if (size > oldrequest) |
| 584 | VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest, |
| 585 | size - oldrequest); |
| 586 | else |
| 587 | VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, |
| 588 | oldsize - size); |
| 589 | |
| 590 | /* set mark to catch clobber of "unused" space */ |
| 591 | if (size < oldsize) |
| 592 | set_sentinel(pointer, size); |
| 593 | #else /* !MEMORY_CONTEXT_CHECKING */ |
| 594 | |
| 595 | /* |
| 596 | * We don't have the information to determine whether we're growing |
| 597 | * the old request or shrinking it, so we conservatively mark the |
| 598 | * entire new allocation DEFINED. |
| 599 | */ |
| 600 | VALGRIND_MAKE_MEM_NOACCESS(pointer, oldsize); |
| 601 | VALGRIND_MAKE_MEM_DEFINED(pointer, size); |
| 602 | #endif |
| 603 | |
| 604 | /* Disallow external access to private part of chunk header. */ |
| 605 | VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 606 | |
| 607 | return pointer; |
| 608 | } |
| 609 | |
| 610 | /* allocate new chunk */ |
| 611 | newPointer = GenerationAlloc((MemoryContext) set, size); |
| 612 | |
| 613 | /* leave immediately if request was not completed */ |
| 614 | if (newPointer == NULL) |
| 615 | { |
| 616 | /* Disallow external access to private part of chunk header. */ |
| 617 | VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 618 | return NULL; |
| 619 | } |
| 620 | |
| 621 | /* |
| 622 | * GenerationAlloc() may have returned a region that is still NOACCESS. |
| 623 | * Change it to UNDEFINED for the moment; memcpy() will then transfer |
| 624 | * definedness from the old allocation to the new. If we know the old |
| 625 | * allocation, copy just that much. Otherwise, make the entire old chunk |
| 626 | * defined to avoid errors as we copy the currently-NOACCESS trailing |
| 627 | * bytes. |
| 628 | */ |
| 629 | VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size); |
| 630 | #ifdef MEMORY_CONTEXT_CHECKING |
| 631 | oldsize = chunk->requested_size; |
| 632 | #else |
| 633 | VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize); |
| 634 | #endif |
| 635 | |
| 636 | /* transfer existing data (certain to fit) */ |
| 637 | memcpy(newPointer, pointer, oldsize); |
| 638 | |
| 639 | /* free old chunk */ |
| 640 | GenerationFree((MemoryContext) set, pointer); |
| 641 | |
| 642 | return newPointer; |
| 643 | } |
| 644 | |
| 645 | /* |
| 646 | * GenerationGetChunkSpace |
| 647 | * Given a currently-allocated chunk, determine the total space |
| 648 | * it occupies (including all memory-allocation overhead). |
| 649 | */ |
| 650 | static Size |
| 651 | GenerationGetChunkSpace(MemoryContext context, void *pointer) |
| 652 | { |
| 653 | GenerationChunk *chunk = GenerationPointerGetChunk(pointer); |
| 654 | Size result; |
| 655 | |
| 656 | VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 657 | result = chunk->size + Generation_CHUNKHDRSZ; |
| 658 | VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 659 | return result; |
| 660 | } |
| 661 | |
| 662 | /* |
| 663 | * GenerationIsEmpty |
| 664 | * Is a GenerationContext empty of any allocated space? |
| 665 | */ |
| 666 | static bool |
| 667 | GenerationIsEmpty(MemoryContext context) |
| 668 | { |
| 669 | GenerationContext *set = (GenerationContext *) context; |
| 670 | |
| 671 | return dlist_is_empty(&set->blocks); |
| 672 | } |
| 673 | |
| 674 | /* |
| 675 | * GenerationStats |
| 676 | * Compute stats about memory consumption of a Generation context. |
| 677 | * |
| 678 | * printfunc: if not NULL, pass a human-readable stats string to this. |
| 679 | * passthru: pass this pointer through to printfunc. |
| 680 | * totals: if not NULL, add stats about this context into *totals. |
| 681 | * |
| 682 | * XXX freespace only accounts for empty space at the end of the block, not |
| 683 | * space of freed chunks (which is unknown). |
| 684 | */ |
| 685 | static void |
| 686 | GenerationStats(MemoryContext context, |
| 687 | MemoryStatsPrintFunc printfunc, void *passthru, |
| 688 | MemoryContextCounters *totals) |
| 689 | { |
| 690 | GenerationContext *set = (GenerationContext *) context; |
| 691 | Size nblocks = 0; |
| 692 | Size nchunks = 0; |
| 693 | Size nfreechunks = 0; |
| 694 | Size totalspace; |
| 695 | Size freespace = 0; |
| 696 | dlist_iter iter; |
| 697 | |
| 698 | /* Include context header in totalspace */ |
| 699 | totalspace = MAXALIGN(sizeof(GenerationContext)); |
| 700 | |
| 701 | dlist_foreach(iter, &set->blocks) |
| 702 | { |
| 703 | GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); |
| 704 | |
| 705 | nblocks++; |
| 706 | nchunks += block->nchunks; |
| 707 | nfreechunks += block->nfree; |
| 708 | totalspace += block->blksize; |
| 709 | freespace += (block->endptr - block->freeptr); |
| 710 | } |
| 711 | |
| 712 | if (printfunc) |
| 713 | { |
| 714 | char stats_string[200]; |
| 715 | |
| 716 | snprintf(stats_string, sizeof(stats_string), |
| 717 | "%zu total in %zd blocks (%zd chunks); %zu free (%zd chunks); %zu used" , |
| 718 | totalspace, nblocks, nchunks, freespace, |
| 719 | nfreechunks, totalspace - freespace); |
| 720 | printfunc(context, passthru, stats_string); |
| 721 | } |
| 722 | |
| 723 | if (totals) |
| 724 | { |
| 725 | totals->nblocks += nblocks; |
| 726 | totals->freechunks += nfreechunks; |
| 727 | totals->totalspace += totalspace; |
| 728 | totals->freespace += freespace; |
| 729 | } |
| 730 | } |
| 731 | |
| 732 | |
| 733 | #ifdef MEMORY_CONTEXT_CHECKING |
| 734 | |
| 735 | /* |
| 736 | * GenerationCheck |
| 737 | * Walk through chunks and check consistency of memory. |
| 738 | * |
| 739 | * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll |
| 740 | * find yourself in an infinite loop when trouble occurs, because this |
| 741 | * routine will be entered again when elog cleanup tries to release memory! |
| 742 | */ |
| 743 | static void |
| 744 | GenerationCheck(MemoryContext context) |
| 745 | { |
| 746 | GenerationContext *gen = (GenerationContext *) context; |
| 747 | const char *name = context->name; |
| 748 | dlist_iter iter; |
| 749 | |
| 750 | /* walk all blocks in this context */ |
| 751 | dlist_foreach(iter, &gen->blocks) |
| 752 | { |
| 753 | GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur); |
| 754 | int nfree, |
| 755 | nchunks; |
| 756 | char *ptr; |
| 757 | |
| 758 | /* |
| 759 | * nfree > nchunks is surely wrong, and we don't expect to see |
| 760 | * equality either, because such a block should have gotten freed. |
| 761 | */ |
| 762 | if (block->nfree >= block->nchunks) |
| 763 | elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated" , |
| 764 | name, block->nfree, block, block->nchunks); |
| 765 | |
| 766 | /* Now walk through the chunks and count them. */ |
| 767 | nfree = 0; |
| 768 | nchunks = 0; |
| 769 | ptr = ((char *) block) + Generation_BLOCKHDRSZ; |
| 770 | |
| 771 | while (ptr < block->freeptr) |
| 772 | { |
| 773 | GenerationChunk *chunk = (GenerationChunk *) ptr; |
| 774 | |
| 775 | /* Allow access to private part of chunk header. */ |
| 776 | VALGRIND_MAKE_MEM_DEFINED(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 777 | |
| 778 | /* move to the next chunk */ |
| 779 | ptr += (chunk->size + Generation_CHUNKHDRSZ); |
| 780 | |
| 781 | nchunks += 1; |
| 782 | |
| 783 | /* chunks have both block and context pointers, so check both */ |
| 784 | if (chunk->block != block) |
| 785 | elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p" , |
| 786 | name, block, chunk); |
| 787 | |
| 788 | /* |
| 789 | * Check for valid context pointer. Note this is an incomplete |
| 790 | * test, since palloc(0) produces an allocated chunk with |
| 791 | * requested_size == 0. |
| 792 | */ |
| 793 | if ((chunk->requested_size > 0 && chunk->context != gen) || |
| 794 | (chunk->context != gen && chunk->context != NULL)) |
| 795 | elog(WARNING, "problem in Generation %s: bogus context link in block %p, chunk %p" , |
| 796 | name, block, chunk); |
| 797 | |
| 798 | /* now make sure the chunk size is correct */ |
| 799 | if (chunk->size < chunk->requested_size || |
| 800 | chunk->size != MAXALIGN(chunk->size)) |
| 801 | elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p" , |
| 802 | name, block, chunk); |
| 803 | |
| 804 | /* is chunk allocated? */ |
| 805 | if (chunk->context != NULL) |
| 806 | { |
| 807 | /* check sentinel, but only in allocated blocks */ |
| 808 | if (chunk->requested_size < chunk->size && |
| 809 | !sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size)) |
| 810 | elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p" , |
| 811 | name, block, chunk); |
| 812 | } |
| 813 | else |
| 814 | nfree += 1; |
| 815 | |
| 816 | /* |
| 817 | * If chunk is allocated, disallow external access to private part |
| 818 | * of chunk header. |
| 819 | */ |
| 820 | if (chunk->context != NULL) |
| 821 | VALGRIND_MAKE_MEM_NOACCESS(chunk, GENERATIONCHUNK_PRIVATE_LEN); |
| 822 | } |
| 823 | |
| 824 | /* |
| 825 | * Make sure we got the expected number of allocated and free chunks |
| 826 | * (as tracked in the block header). |
| 827 | */ |
| 828 | if (nchunks != block->nchunks) |
| 829 | elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d" , |
| 830 | name, nchunks, block, block->nchunks); |
| 831 | |
| 832 | if (nfree != block->nfree) |
| 833 | elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d" , |
| 834 | name, nfree, block, block->nfree); |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | #endif /* MEMORY_CONTEXT_CHECKING */ |
| 839 | |