Age Owner Branch data TLA Line data Source code
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-2025, 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. The block is classed as empty when the
24 : : * number of free chunks is equal to the number of allocated chunks. When
25 : : * this occurs, instead of freeing the block, we try to "recycle" it, i.e.
26 : : * reuse it for new allocations. This is done by setting the block in the
27 : : * context's 'freeblock' field. If the freeblock field is already occupied
28 : : * by another free block we simply return the newly empty block to malloc.
29 : : *
30 : : * This approach to free blocks requires fewer malloc/free calls for truly
31 : : * first allocated, first free'd allocation patterns.
32 : : *
33 : : *-------------------------------------------------------------------------
34 : : */
35 : :
36 : : #include "postgres.h"
37 : :
38 : : #include "lib/ilist.h"
39 : : #include "port/pg_bitutils.h"
40 : : #include "utils/memdebug.h"
41 : : #include "utils/memutils.h"
42 : : #include "utils/memutils_internal.h"
43 : : #include "utils/memutils_memorychunk.h"
44 : :
45 : :
46 : : #define Generation_BLOCKHDRSZ MAXALIGN(sizeof(GenerationBlock))
47 : : #define Generation_CHUNKHDRSZ sizeof(MemoryChunk)
48 : : #define FIRST_BLOCKHDRSZ (MAXALIGN(sizeof(GenerationContext)) + \
49 : : Generation_BLOCKHDRSZ)
50 : :
51 : : #define Generation_CHUNK_FRACTION 8
52 : :
53 : : typedef struct GenerationBlock GenerationBlock; /* forward reference */
54 : :
55 : : typedef void *GenerationPointer;
56 : :
57 : : /*
58 : : * GenerationContext is a simple memory context not reusing allocated chunks,
59 : : * and freeing blocks once all chunks are freed.
60 : : */
61 : : typedef struct GenerationContext
62 : : {
63 : : MemoryContextData header; /* Standard memory-context fields */
64 : :
65 : : /* Generational context parameters */
66 : : uint32 initBlockSize; /* initial block size */
67 : : uint32 maxBlockSize; /* maximum block size */
68 : : uint32 nextBlockSize; /* next block size to allocate */
69 : : uint32 allocChunkLimit; /* effective chunk size limit */
70 : :
71 : : GenerationBlock *block; /* current (most recently allocated) block */
72 : : GenerationBlock *freeblock; /* pointer to an empty block that's being
73 : : * recycled, or NULL if there's no such block. */
74 : : dlist_head blocks; /* list of blocks */
75 : : } GenerationContext;
76 : :
77 : : /*
78 : : * GenerationBlock
79 : : * GenerationBlock is the unit of memory that is obtained by generation.c
80 : : * from malloc(). It contains zero or more MemoryChunks, which are the
81 : : * units requested by palloc() and freed by pfree(). MemoryChunks cannot
82 : : * be returned to malloc() individually, instead pfree() updates the free
83 : : * counter of the block and when all chunks in a block are free the whole
84 : : * block can be returned to malloc().
85 : : *
86 : : * GenerationBlock is the header data for a block --- the usable space
87 : : * within the block begins at the next alignment boundary.
88 : : */
89 : : struct GenerationBlock
90 : : {
91 : : dlist_node node; /* doubly-linked list of blocks */
92 : : GenerationContext *context; /* pointer back to the owning context */
93 : : Size blksize; /* allocated size of this block */
94 : : int nchunks; /* number of chunks in the block */
95 : : int nfree; /* number of free chunks */
96 : : char *freeptr; /* start of free space in this block */
97 : : char *endptr; /* end of space in this block */
98 : : };
99 : :
100 : : /*
101 : : * GenerationIsValid
102 : : * True iff set is valid generation set.
103 : : */
104 : : #define GenerationIsValid(set) \
105 : : (PointerIsValid(set) && IsA(set, GenerationContext))
106 : :
107 : : /*
108 : : * GenerationBlockIsValid
109 : : * True iff block is valid block of generation set.
110 : : */
111 : : #define GenerationBlockIsValid(block) \
112 : : (PointerIsValid(block) && GenerationIsValid((block)->context))
113 : :
114 : : /*
115 : : * GenerationBlockIsEmpty
116 : : * True iff block contains no chunks
117 : : */
118 : : #define GenerationBlockIsEmpty(b) ((b)->nchunks == 0)
119 : :
120 : : /*
121 : : * We always store external chunks on a dedicated block. This makes fetching
122 : : * the block from an external chunk easy since it's always the first and only
123 : : * chunk on the block.
124 : : */
125 : : #define ExternalChunkGetBlock(chunk) \
126 : : (GenerationBlock *) ((char *) chunk - Generation_BLOCKHDRSZ)
127 : :
128 : : /* Obtain the keeper block for a generation context */
129 : : #define KeeperBlock(set) \
130 : : ((GenerationBlock *) (((char *) set) + \
131 : : MAXALIGN(sizeof(GenerationContext))))
132 : :
133 : : /* Check if the block is the keeper block of the given generation context */
134 : : #define IsKeeperBlock(set, block) ((block) == (KeeperBlock(set)))
135 : :
136 : : /* Inlined helper functions */
137 : : static inline void GenerationBlockInit(GenerationContext *context,
138 : : GenerationBlock *block,
139 : : Size blksize);
140 : : static inline void GenerationBlockMarkEmpty(GenerationBlock *block);
141 : : static inline Size GenerationBlockFreeBytes(GenerationBlock *block);
142 : : static inline void GenerationBlockFree(GenerationContext *set,
143 : : GenerationBlock *block);
144 : :
145 : :
146 : : /*
147 : : * Public routines
148 : : */
149 : :
150 : :
151 : : /*
152 : : * GenerationContextCreate
153 : : * Create a new Generation context.
154 : : *
155 : : * parent: parent context, or NULL if top-level context
156 : : * name: name of context (must be statically allocated)
157 : : * minContextSize: minimum context size
158 : : * initBlockSize: initial allocation block size
159 : : * maxBlockSize: maximum allocation block size
160 : : */
161 : : MemoryContext
2844 simon@2ndQuadrant.co 162 :CBC 116151 : GenerationContextCreate(MemoryContext parent,
163 : : const char *name,
164 : : Size minContextSize,
165 : : Size initBlockSize,
166 : : Size maxBlockSize)
167 : : {
168 : : Size firstBlockSize;
169 : : Size allocSize;
170 : : GenerationContext *set;
171 : : GenerationBlock *block;
172 : :
173 : : /* ensure MemoryChunk's size is properly maxaligned */
174 : : StaticAssertDecl(Generation_CHUNKHDRSZ == MAXALIGN(Generation_CHUNKHDRSZ),
175 : : "sizeof(MemoryChunk) is not maxaligned");
176 : :
177 : : /*
178 : : * First, validate allocation parameters. Asserts seem sufficient because
179 : : * nobody varies their parameters at runtime. We somewhat arbitrarily
180 : : * enforce a minimum 1K block size. We restrict the maximum block size to
181 : : * MEMORYCHUNK_MAX_BLOCKOFFSET as MemoryChunks are limited to this in
182 : : * regards to addressing the offset between the chunk and the block that
183 : : * the chunk is stored on. We would be unable to store the offset between
184 : : * the chunk and block for any chunks that were beyond
185 : : * MEMORYCHUNK_MAX_BLOCKOFFSET bytes into the block if the block was to be
186 : : * larger than this.
187 : : */
1251 drowley@postgresql.o 188 [ + - - + ]: 116151 : Assert(initBlockSize == MAXALIGN(initBlockSize) &&
189 : : initBlockSize >= 1024);
190 [ + - + - : 116151 : Assert(maxBlockSize == MAXALIGN(maxBlockSize) &&
- + ]
191 : : maxBlockSize >= initBlockSize &&
192 : : AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
193 [ + + + - : 116151 : Assert(minContextSize == 0 ||
+ - - + ]
194 : : (minContextSize == MAXALIGN(minContextSize) &&
195 : : minContextSize >= 1024 &&
196 : : minContextSize <= maxBlockSize));
1104 197 [ - + ]: 116151 : Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
198 : :
199 : : /* Determine size of initial block */
1251 200 : 116151 : allocSize = MAXALIGN(sizeof(GenerationContext)) +
201 : : Generation_BLOCKHDRSZ + Generation_CHUNKHDRSZ;
202 [ + + ]: 116151 : if (minContextSize != 0)
203 : 1098 : allocSize = Max(allocSize, minContextSize);
204 : : else
205 : 115053 : allocSize = Max(allocSize, initBlockSize);
206 : :
207 : : /*
208 : : * Allocate the initial block. Unlike other generation.c blocks, it
209 : : * starts with the context header and its block header follows that.
210 : : */
211 : 116151 : set = (GenerationContext *) malloc(allocSize);
2824 tgl@sss.pgh.pa.us 212 [ - + ]: 116151 : if (set == NULL)
213 : : {
2824 tgl@sss.pgh.pa.us 214 :UBC 0 : MemoryContextStats(TopMemoryContext);
215 [ # # ]: 0 : ereport(ERROR,
216 : : (errcode(ERRCODE_OUT_OF_MEMORY),
217 : : errmsg("out of memory"),
218 : : errdetail("Failed while creating memory context \"%s\".",
219 : : name)));
220 : : }
221 : :
222 : : /*
223 : : * Avoid writing code that can fail between here and MemoryContextCreate;
224 : : * we'd leak the header if we ereport in this stretch.
225 : : */
226 : :
227 : : /* See comments about Valgrind interactions in aset.c */
228 : : VALGRIND_CREATE_MEMPOOL(set, 0, false);
229 : : /* This vchunk covers the GenerationContext and the keeper block header */
230 : : VALGRIND_MEMPOOL_ALLOC(set, set, FIRST_BLOCKHDRSZ);
231 : :
1251 drowley@postgresql.o 232 :CBC 116151 : dlist_init(&set->blocks);
233 : :
234 : : /* Fill in the initial block's block header */
782 235 : 116151 : block = KeeperBlock(set);
236 : : /* determine the block size and initialize it */
1251 237 : 116151 : firstBlockSize = allocSize - MAXALIGN(sizeof(GenerationContext));
1104 238 : 116151 : GenerationBlockInit(set, block, firstBlockSize);
239 : :
240 : : /* add it to the doubly-linked list of blocks */
1251 241 : 116151 : dlist_push_head(&set->blocks, &block->node);
242 : :
243 : : /* use it as the current allocation block */
244 : 116151 : set->block = block;
245 : :
246 : : /* No free block, yet */
247 : 116151 : set->freeblock = NULL;
248 : :
249 : : /* Fill in GenerationContext-specific header fields */
782 250 : 116151 : set->initBlockSize = (uint32) initBlockSize;
251 : 116151 : set->maxBlockSize = (uint32) maxBlockSize;
252 : 116151 : set->nextBlockSize = (uint32) initBlockSize;
253 : :
254 : : /*
255 : : * Compute the allocation chunk size limit for this context.
256 : : *
257 : : * Limit the maximum size a non-dedicated chunk can be so that we can fit
258 : : * at least Generation_CHUNK_FRACTION of chunks this big onto the maximum
259 : : * sized block. We must further limit this value so that it's no more
260 : : * than MEMORYCHUNK_MAX_VALUE. We're unable to have non-external chunks
261 : : * larger than that value as we store the chunk size in the MemoryChunk
262 : : * 'value' field in the call to MemoryChunkSetHdrMask().
263 : : */
1104 264 : 116151 : set->allocChunkLimit = Min(maxBlockSize, MEMORYCHUNK_MAX_VALUE);
1251 265 : 116151 : while ((Size) (set->allocChunkLimit + Generation_CHUNKHDRSZ) >
266 [ + + ]: 580755 : (Size) ((Size) (maxBlockSize - Generation_BLOCKHDRSZ) / Generation_CHUNK_FRACTION))
267 : 464604 : set->allocChunkLimit >>= 1;
268 : :
269 : : /* Finally, do the type-independent part of context creation */
2824 tgl@sss.pgh.pa.us 270 : 116151 : MemoryContextCreate((MemoryContext) set,
271 : : T_GenerationContext,
272 : : MCTX_GENERATION_ID,
273 : : parent,
274 : : name);
275 : :
1251 drowley@postgresql.o 276 : 116151 : ((MemoryContext) set)->mem_allocated = firstBlockSize;
277 : :
2824 tgl@sss.pgh.pa.us 278 : 116151 : return (MemoryContext) set;
279 : : }
280 : :
281 : : /*
282 : : * GenerationReset
283 : : * Frees all memory which is allocated in the given set.
284 : : *
285 : : * The initial "keeper" block (which shares a malloc chunk with the context
286 : : * header) is not given back to the operating system though. In this way, we
287 : : * don't thrash malloc() when a context is repeatedly reset after small
288 : : * allocations.
289 : : */
290 : : void
2844 simon@2ndQuadrant.co 291 : 121094 : GenerationReset(MemoryContext context)
292 : : {
2838 rhaas@postgresql.org 293 : 121094 : GenerationContext *set = (GenerationContext *) context;
294 : : dlist_mutable_iter miter;
295 : :
1044 peter@eisentraut.org 296 [ + - - + ]: 121094 : Assert(GenerationIsValid(set));
297 : :
298 : : #ifdef MEMORY_CONTEXT_CHECKING
299 : : /* Check for corruption and leaks before freeing */
2844 simon@2ndQuadrant.co 300 : 121094 : GenerationCheck(context);
301 : : #endif
302 : :
303 : : /*
304 : : * NULLify the free block pointer. We must do this before calling
305 : : * GenerationBlockFree as that function never expects to free the
306 : : * freeblock.
307 : : */
1251 drowley@postgresql.o 308 : 121094 : set->freeblock = NULL;
309 : :
2844 simon@2ndQuadrant.co 310 [ + - + + ]: 254235 : dlist_foreach_modify(miter, &set->blocks)
311 : : {
312 : 133141 : GenerationBlock *block = dlist_container(GenerationBlock, node, miter.cur);
313 : :
782 drowley@postgresql.o 314 [ + + ]: 133141 : if (IsKeeperBlock(set, block))
1251 315 : 121094 : GenerationBlockMarkEmpty(block);
316 : : else
317 : 12047 : GenerationBlockFree(set, block);
318 : : }
319 : :
320 : : /*
321 : : * Instruct Valgrind to throw away all the vchunks associated with this
322 : : * context, except for the one covering the GenerationContext and
323 : : * keeper-block header. This gets rid of the vchunks for whatever user
324 : : * data is getting discarded by the context reset.
325 : : */
326 : : VALGRIND_MEMPOOL_TRIM(set, set, FIRST_BLOCKHDRSZ);
327 : :
328 : : /* set it so new allocations to make use of the keeper block */
782 329 : 121094 : set->block = KeeperBlock(set);
330 : :
331 : : /* Reset block size allocation sequence, too */
1251 332 : 121094 : set->nextBlockSize = set->initBlockSize;
333 : :
334 : : /* Ensure there is only 1 item in the dlist */
335 [ - + ]: 121094 : Assert(!dlist_is_empty(&set->blocks));
336 [ - + ]: 121094 : Assert(!dlist_has_next(&set->blocks, dlist_head_node(&set->blocks)));
2844 simon@2ndQuadrant.co 337 : 121094 : }
338 : :
339 : : /*
340 : : * GenerationDelete
341 : : * Free all memory which is allocated in the given context.
342 : : */
343 : : void
344 : 115934 : GenerationDelete(MemoryContext context)
345 : : {
346 : : /* Reset to release all releasable GenerationBlocks */
347 : 115934 : GenerationReset(context);
348 : :
349 : : /* Destroy the vpool -- see notes in aset.c */
350 : : VALGRIND_DESTROY_MEMPOOL(context);
351 : :
352 : : /* And free the context header and keeper block */
2824 tgl@sss.pgh.pa.us 353 : 115934 : free(context);
2844 simon@2ndQuadrant.co 354 : 115934 : }
355 : :
356 : : /*
357 : : * Helper for GenerationAlloc() that allocates an entire block for the chunk.
358 : : *
359 : : * GenerationAlloc()'s comment explains why this is separate.
360 : : */
361 : : pg_noinline
362 : : static void *
551 drowley@postgresql.o 363 : 4618 : GenerationAllocLarge(MemoryContext context, Size size, int flags)
364 : : {
2838 rhaas@postgresql.org 365 : 4618 : GenerationContext *set = (GenerationContext *) context;
366 : : GenerationBlock *block;
367 : : MemoryChunk *chunk;
368 : : Size chunk_size;
369 : : Size required_size;
370 : : Size blksize;
371 : :
372 : : /* validate 'size' is within the limits for the given 'flags' */
551 drowley@postgresql.o 373 : 4618 : MemoryContextCheckSize(context, size, flags);
374 : :
375 : : #ifdef MEMORY_CONTEXT_CHECKING
376 : : /* ensure there's always space for the sentinel byte */
1095 377 : 4618 : chunk_size = MAXALIGN(size + 1);
378 : : #else
379 : : chunk_size = MAXALIGN(size);
380 : : #endif
381 : 4618 : required_size = chunk_size + Generation_CHUNKHDRSZ;
551 382 : 4618 : blksize = required_size + Generation_BLOCKHDRSZ;
383 : :
384 : 4618 : block = (GenerationBlock *) malloc(blksize);
385 [ - + ]: 4618 : if (block == NULL)
551 drowley@postgresql.o 386 :UBC 0 : return MemoryContextAllocationFailure(context, size, flags);
387 : :
388 : : /* Make a vchunk covering the new block's header */
389 : : VALGRIND_MEMPOOL_ALLOC(set, block, Generation_BLOCKHDRSZ);
390 : :
551 drowley@postgresql.o 391 :CBC 4618 : context->mem_allocated += blksize;
392 : :
393 : : /* block with a single (used) chunk */
394 : 4618 : block->context = set;
395 : 4618 : block->blksize = blksize;
396 : 4618 : block->nchunks = 1;
397 : 4618 : block->nfree = 0;
398 : :
399 : : /* the block is completely full */
400 : 4618 : block->freeptr = block->endptr = ((char *) block) + blksize;
401 : :
402 : 4618 : chunk = (MemoryChunk *) (((char *) block) + Generation_BLOCKHDRSZ);
403 : :
404 : : /* mark the MemoryChunk as externally managed */
405 : 4618 : MemoryChunkSetHdrMaskExternal(chunk, MCTX_GENERATION_ID);
406 : :
407 : : #ifdef MEMORY_CONTEXT_CHECKING
408 : 4618 : chunk->requested_size = size;
409 : : /* set mark to catch clobber of "unused" space */
410 [ - + ]: 4618 : Assert(size < chunk_size);
411 : 4618 : set_sentinel(MemoryChunkGetPointer(chunk), size);
412 : : #endif
413 : : #ifdef RANDOMIZE_ALLOCATED_MEMORY
414 : : /* fill the allocated space with junk */
415 : : randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
416 : : #endif
417 : :
418 : : /* add the block to the list of allocated blocks */
419 : 4618 : dlist_push_head(&set->blocks, &block->node);
420 : :
421 : : /* Ensure any padding bytes are marked NOACCESS. */
422 : : VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
423 : : chunk_size - size);
424 : :
425 : : /* Disallow access to the chunk header. */
426 : : VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
427 : :
428 : 4618 : return MemoryChunkGetPointer(chunk);
429 : : }
430 : :
431 : : /*
432 : : * Small helper for allocating a new chunk from a chunk, to avoid duplicating
433 : : * the code between GenerationAlloc() and GenerationAllocFromNewBlock().
434 : : */
435 : : static inline void *
436 : 12431744 : GenerationAllocChunkFromBlock(MemoryContext context, GenerationBlock *block,
437 : : Size size, Size chunk_size)
438 : : {
439 : 12431744 : MemoryChunk *chunk = (MemoryChunk *) (block->freeptr);
440 : :
441 : : /* validate we've been given a block with enough free space */
2844 simon@2ndQuadrant.co 442 [ - + ]: 12431744 : Assert(block != NULL);
551 drowley@postgresql.o 443 [ - + ]: 12431744 : Assert((block->endptr - block->freeptr) >=
444 : : Generation_CHUNKHDRSZ + chunk_size);
445 : :
446 : : /* Prepare to initialize the chunk header. */
447 : : VALGRIND_MAKE_MEM_UNDEFINED(chunk, Generation_CHUNKHDRSZ);
448 : :
2844 simon@2ndQuadrant.co 449 : 12431744 : block->nchunks += 1;
450 : 12431744 : block->freeptr += (Generation_CHUNKHDRSZ + chunk_size);
451 : :
2843 tgl@sss.pgh.pa.us 452 [ - + ]: 12431744 : Assert(block->freeptr <= block->endptr);
453 : :
1104 drowley@postgresql.o 454 : 12431744 : MemoryChunkSetHdrMask(chunk, block, chunk_size, MCTX_GENERATION_ID);
455 : : #ifdef MEMORY_CONTEXT_CHECKING
2844 simon@2ndQuadrant.co 456 : 12431744 : chunk->requested_size = size;
457 : : /* set mark to catch clobber of "unused" space */
1095 drowley@postgresql.o 458 [ - + ]: 12431744 : Assert(size < chunk_size);
459 : 12431744 : set_sentinel(MemoryChunkGetPointer(chunk), size);
460 : : #endif
461 : : #ifdef RANDOMIZE_ALLOCATED_MEMORY
462 : : /* fill the allocated space with junk */
463 : : randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
464 : : #endif
465 : :
466 : : /* Ensure any padding bytes are marked NOACCESS. */
467 : : VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
468 : : chunk_size - size);
469 : :
470 : : /* Disallow access to the chunk header. */
471 : : VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
472 : :
1104 473 : 12431744 : return MemoryChunkGetPointer(chunk);
474 : : }
475 : :
476 : : /*
477 : : * Helper for GenerationAlloc() that allocates a new block and returns a chunk
478 : : * allocated from it.
479 : : *
480 : : * GenerationAlloc()'s comment explains why this is separate.
481 : : */
482 : : pg_noinline
483 : : static void *
551 484 : 23341 : GenerationAllocFromNewBlock(MemoryContext context, Size size, int flags,
485 : : Size chunk_size)
486 : : {
487 : 23341 : GenerationContext *set = (GenerationContext *) context;
488 : : GenerationBlock *block;
489 : : Size blksize;
490 : : Size required_size;
491 : :
492 : : /*
493 : : * The first such block has size initBlockSize, and we double the space in
494 : : * each succeeding block, but not more than maxBlockSize.
495 : : */
496 : 23341 : blksize = set->nextBlockSize;
497 : 23341 : set->nextBlockSize <<= 1;
498 [ + + ]: 23341 : if (set->nextBlockSize > set->maxBlockSize)
499 : 11231 : set->nextBlockSize = set->maxBlockSize;
500 : :
501 : : /* we'll need space for the chunk, chunk hdr and block hdr */
502 : 23341 : required_size = chunk_size + Generation_CHUNKHDRSZ + Generation_BLOCKHDRSZ;
503 : :
504 : : /* round the size up to the next power of 2 */
505 [ + + ]: 23341 : if (blksize < required_size)
506 : 63 : blksize = pg_nextpower2_size_t(required_size);
507 : :
508 : 23341 : block = (GenerationBlock *) malloc(blksize);
509 : :
510 [ - + ]: 23341 : if (block == NULL)
551 drowley@postgresql.o 511 :UBC 0 : return MemoryContextAllocationFailure(context, size, flags);
512 : :
513 : : /* Make a vchunk covering the new block's header */
514 : : VALGRIND_MEMPOOL_ALLOC(set, block, Generation_BLOCKHDRSZ);
515 : :
551 drowley@postgresql.o 516 :CBC 23341 : context->mem_allocated += blksize;
517 : :
518 : : /* initialize the new block */
519 : 23341 : GenerationBlockInit(set, block, blksize);
520 : :
521 : : /* add it to the doubly-linked list of blocks */
522 : 23341 : dlist_push_head(&set->blocks, &block->node);
523 : :
524 : : /* make this the current block */
525 : 23341 : set->block = block;
526 : :
527 : 23341 : return GenerationAllocChunkFromBlock(context, block, size, chunk_size);
528 : : }
529 : :
530 : : /*
531 : : * GenerationAlloc
532 : : * Returns a pointer to allocated memory of given size or raises an ERROR
533 : : * on allocation failure, or returns NULL when flags contains
534 : : * MCXT_ALLOC_NO_OOM.
535 : : *
536 : : * No request may exceed:
537 : : * MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ
538 : : * All callers use a much-lower limit.
539 : : *
540 : : * Note: when using valgrind, it doesn't matter how the returned allocation
541 : : * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will
542 : : * return space that is marked NOACCESS - GenerationRealloc has to beware!
543 : : *
544 : : * This function should only contain the most common code paths. Everything
545 : : * else should be in pg_noinline helper functions, thus avoiding the overhead
546 : : * of creating a stack frame for the common cases. Allocating memory is often
547 : : * a bottleneck in many workloads, so avoiding stack frame setup is
548 : : * worthwhile. Helper functions should always directly return the newly
549 : : * allocated memory so that we can just return that address directly as a tail
550 : : * call.
551 : : */
552 : : void *
553 : 12436362 : GenerationAlloc(MemoryContext context, Size size, int flags)
554 : : {
555 : 12436362 : GenerationContext *set = (GenerationContext *) context;
556 : : GenerationBlock *block;
557 : : Size chunk_size;
558 : : Size required_size;
559 : :
560 [ + - - + ]: 12436362 : Assert(GenerationIsValid(set));
561 : :
562 : : #ifdef MEMORY_CONTEXT_CHECKING
563 : : /* ensure there's always space for the sentinel byte */
564 : 12436362 : chunk_size = MAXALIGN(size + 1);
565 : : #else
566 : : chunk_size = MAXALIGN(size);
567 : : #endif
568 : :
569 : : /*
570 : : * If requested size exceeds maximum for chunks we hand the request off to
571 : : * GenerationAllocLarge().
572 : : */
573 [ + + ]: 12436362 : if (chunk_size > set->allocChunkLimit)
574 : 4618 : return GenerationAllocLarge(context, size, flags);
575 : :
576 : 12431744 : required_size = chunk_size + Generation_CHUNKHDRSZ;
577 : :
578 : : /*
579 : : * Not an oversized chunk. We try to first make use of the current block,
580 : : * but if there's not enough space in it, instead of allocating a new
581 : : * block, we look to see if the empty freeblock has enough space. We
582 : : * don't try reusing the keeper block. If it's become empty we'll reuse
583 : : * that again only if the context is reset.
584 : : *
585 : : * We only try reusing the freeblock if we've no space for this allocation
586 : : * on the current block. When a freeblock exists, we'll switch to it once
587 : : * the first time we can't fit an allocation in the current block. We
588 : : * avoid ping-ponging between the two as we need to be careful not to
589 : : * fragment differently sized consecutive allocations between several
590 : : * blocks. Going between the two could cause fragmentation for FIFO
591 : : * workloads, which generation is meant to be good at.
592 : : */
593 : 12431744 : block = set->block;
594 : :
595 [ + + ]: 12431744 : if (unlikely(GenerationBlockFreeBytes(block) < required_size))
596 : : {
597 : 27782 : GenerationBlock *freeblock = set->freeblock;
598 : :
599 : : /* freeblock, if set, must be empty */
600 [ + + - + ]: 27782 : Assert(freeblock == NULL || GenerationBlockIsEmpty(freeblock));
601 : :
602 : : /* check if we have a freeblock and if it's big enough */
603 [ + + + - ]: 32223 : if (freeblock != NULL &&
604 : 4441 : GenerationBlockFreeBytes(freeblock) >= required_size)
605 : : {
606 : : /* make the freeblock the current block */
607 : 4441 : set->freeblock = NULL;
608 : 4441 : set->block = freeblock;
609 : :
610 : 4441 : return GenerationAllocChunkFromBlock(context,
611 : : freeblock,
612 : : size,
613 : : chunk_size);
614 : : }
615 : : else
616 : : {
617 : : /*
618 : : * No freeblock, or it's not big enough for this allocation. Make
619 : : * a new block.
620 : : */
621 : 23341 : return GenerationAllocFromNewBlock(context, size, flags, chunk_size);
622 : : }
623 : : }
624 : :
625 : : /* The current block has space, so just allocate chunk there. */
626 : 12403962 : return GenerationAllocChunkFromBlock(context, block, size, chunk_size);
627 : : }
628 : :
629 : : /*
630 : : * GenerationBlockInit
631 : : * Initializes 'block' assuming 'blksize'. Does not update the context's
632 : : * mem_allocated field.
633 : : */
634 : : static inline void
1104 635 : 139492 : GenerationBlockInit(GenerationContext *context, GenerationBlock *block,
636 : : Size blksize)
637 : : {
638 : 139492 : block->context = context;
1251 639 : 139492 : block->blksize = blksize;
640 : 139492 : block->nchunks = 0;
641 : 139492 : block->nfree = 0;
642 : :
643 : 139492 : block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
644 : 139492 : block->endptr = ((char *) block) + blksize;
645 : :
646 : : /* Mark unallocated space NOACCESS. */
647 : : VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
648 : : blksize - Generation_BLOCKHDRSZ);
649 : 139492 : }
650 : :
651 : : /*
652 : : * GenerationBlockMarkEmpty
653 : : * Set a block as empty. Does not free the block.
654 : : */
655 : : static inline void
656 : 3616527 : GenerationBlockMarkEmpty(GenerationBlock *block)
657 : : {
658 : : #if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
659 : 3616527 : char *datastart = ((char *) block) + Generation_BLOCKHDRSZ;
660 : : #endif
661 : :
662 : : #ifdef CLOBBER_FREED_MEMORY
663 : 3616527 : wipe_mem(datastart, block->freeptr - datastart);
664 : : #else
665 : : /* wipe_mem() would have done this */
666 : : VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
667 : : #endif
668 : :
669 : : /* Reset the block, but don't return it to malloc */
670 : 3616527 : block->nchunks = 0;
671 : 3616527 : block->nfree = 0;
672 : 3616527 : block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
673 : 3616527 : }
674 : :
675 : : /*
676 : : * GenerationBlockFreeBytes
677 : : * Returns the number of bytes free in 'block'
678 : : */
679 : : static inline Size
680 : 12436185 : GenerationBlockFreeBytes(GenerationBlock *block)
681 : : {
682 : 12436185 : return (block->endptr - block->freeptr);
683 : : }
684 : :
685 : : /*
686 : : * GenerationBlockFree
687 : : * Remove 'block' from 'set' and release the memory consumed by it.
688 : : */
689 : : static inline void
690 : 27929 : GenerationBlockFree(GenerationContext *set, GenerationBlock *block)
691 : : {
692 : : /* Make sure nobody tries to free the keeper block */
782 693 [ - + ]: 27929 : Assert(!IsKeeperBlock(set, block));
694 : : /* We shouldn't be freeing the freeblock either */
1251 695 [ - + ]: 27929 : Assert(block != set->freeblock);
696 : :
697 : : /* release the block from the list of blocks */
698 : 27929 : dlist_delete(&block->node);
699 : :
700 : 27929 : ((MemoryContext) set)->mem_allocated -= block->blksize;
701 : :
702 : : #ifdef CLOBBER_FREED_MEMORY
703 : 27929 : wipe_mem(block, block->blksize);
704 : : #endif
705 : :
706 : : /* As in aset.c, free block-header vchunks explicitly */
707 : : VALGRIND_MEMPOOL_FREE(set, block);
708 : :
709 : 27929 : free(block);
710 : 27929 : }
711 : :
712 : : /*
713 : : * GenerationFree
714 : : * Update number of chunks in the block, and consider freeing the block
715 : : * if it's become empty.
716 : : */
717 : : void
1104 718 : 6976640 : GenerationFree(void *pointer)
719 : : {
720 : 6976640 : MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
721 : : GenerationBlock *block;
722 : : GenerationContext *set;
723 : : #if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
724 : : || defined(CLOBBER_FREED_MEMORY)
725 : : Size chunksize;
726 : : #endif
727 : :
728 : : /* Allow access to the chunk header. */
729 : : VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ);
730 : :
731 [ + + ]: 6976640 : if (MemoryChunkIsExternal(chunk))
732 : : {
733 : 4530 : block = ExternalChunkGetBlock(chunk);
734 : :
735 : : /*
736 : : * Try to verify that we have a sane block pointer: the block header
737 : : * should reference a generation context.
738 : : */
1062 tgl@sss.pgh.pa.us 739 [ + - + - : 4530 : if (!GenerationBlockIsValid(block))
- + ]
1062 tgl@sss.pgh.pa.us 740 [ # # ]:UBC 0 : elog(ERROR, "could not find block containing chunk %p", chunk);
741 : :
742 : : #if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
743 : : || defined(CLOBBER_FREED_MEMORY)
1104 drowley@postgresql.o 744 :CBC 4530 : chunksize = block->endptr - (char *) pointer;
745 : : #endif
746 : : }
747 : : else
748 : : {
749 : 6972110 : block = MemoryChunkGetBlock(chunk);
750 : :
751 : : /*
752 : : * In this path, for speed reasons we just Assert that the referenced
753 : : * block is good. Future field experience may show that this Assert
754 : : * had better become a regular runtime test-and-elog check.
755 : : */
1044 peter@eisentraut.org 756 [ + - + - : 6972110 : Assert(GenerationBlockIsValid(block));
- + ]
757 : :
758 : : #if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
759 : : || defined(CLOBBER_FREED_MEMORY)
1104 drowley@postgresql.o 760 : 6972110 : chunksize = MemoryChunkGetValue(chunk);
761 : : #endif
762 : : }
763 : :
764 : : #ifdef MEMORY_CONTEXT_CHECKING
765 : : /* Test for someone scribbling on unused space in chunk */
1095 766 [ - + ]: 6976640 : Assert(chunk->requested_size < chunksize);
767 [ - + ]: 6976640 : if (!sentinel_ok(pointer, chunk->requested_size))
1095 drowley@postgresql.o 768 [ # # ]:UBC 0 : elog(WARNING, "detected write past chunk end in %s %p",
769 : : ((MemoryContext) block->context)->name, chunk);
770 : : #endif
771 : :
772 : : #ifdef CLOBBER_FREED_MEMORY
1104 drowley@postgresql.o 773 :CBC 6976640 : wipe_mem(pointer, chunksize);
774 : : #endif
775 : :
776 : : #ifdef MEMORY_CONTEXT_CHECKING
777 : : /* Reset requested_size to InvalidAllocSize in freed chunks */
778 : 6976640 : chunk->requested_size = InvalidAllocSize;
779 : : #endif
780 : :
2844 simon@2ndQuadrant.co 781 : 6976640 : block->nfree += 1;
782 : :
783 [ - + ]: 6976640 : Assert(block->nchunks > 0);
784 [ - + ]: 6976640 : Assert(block->nfree <= block->nchunks);
551 drowley@postgresql.o 785 [ - + ]: 6976640 : Assert(block != block->context->freeblock);
786 : :
787 : : /* If there are still allocated chunks in the block, we're done. */
788 [ + + ]: 6976640 : if (likely(block->nfree < block->nchunks))
2844 simon@2ndQuadrant.co 789 : 3465325 : return;
790 : :
1104 drowley@postgresql.o 791 : 3511315 : set = block->context;
792 : :
793 : : /*-----------------------
794 : : * The block this allocation was on has now become completely empty of
795 : : * chunks. In the general case, we can now return the memory for this
796 : : * block back to malloc. However, there are cases where we don't want to
797 : : * do that:
798 : : *
799 : : * 1) If it's the keeper block. This block was malloc'd in the same
800 : : * allocation as the context itself and can't be free'd without
801 : : * freeing the context.
802 : : * 2) If it's the current block. We could free this, but doing so would
803 : : * leave us nothing to set the current block to, so we just mark the
804 : : * block as empty so new allocations can reuse it again.
805 : : * 3) If we have no "freeblock" set, then we save a single block for
806 : : * future allocations to avoid having to malloc a new block again.
807 : : * This is useful for FIFO workloads as it avoids continual
808 : : * free/malloc cycles.
809 : : */
551 810 [ + + + + ]: 3511315 : if (IsKeeperBlock(set, block) || set->block == block)
811 : 3490772 : GenerationBlockMarkEmpty(block); /* case 1 and 2 */
812 [ + + ]: 20543 : else if (set->freeblock == NULL)
813 : : {
814 : : /* case 3 */
1251 815 : 4661 : GenerationBlockMarkEmpty(block);
551 816 : 4661 : set->freeblock = block;
817 : : }
818 : : else
819 : 15882 : GenerationBlockFree(set, block); /* Otherwise, free it */
820 : : }
821 : :
822 : : /*
823 : : * GenerationRealloc
824 : : * When handling repalloc, we simply allocate a new chunk, copy the data
825 : : * and discard the old one. The only exception is when the new size fits
826 : : * into the old chunk - in that case we just update chunk header.
827 : : */
828 : : void *
557 drowley@postgresql.o 829 :UBC 0 : GenerationRealloc(void *pointer, Size size, int flags)
830 : : {
1104 831 : 0 : MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
832 : : GenerationContext *set;
833 : : GenerationBlock *block;
834 : : GenerationPointer newPointer;
835 : : Size oldsize;
836 : :
837 : : /* Allow access to the chunk header. */
838 : : VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ);
839 : :
840 [ # # ]: 0 : if (MemoryChunkIsExternal(chunk))
841 : : {
842 : 0 : block = ExternalChunkGetBlock(chunk);
843 : :
844 : : /*
845 : : * Try to verify that we have a sane block pointer: the block header
846 : : * should reference a generation context.
847 : : */
1062 tgl@sss.pgh.pa.us 848 [ # # # # : 0 : if (!GenerationBlockIsValid(block))
# # ]
849 [ # # ]: 0 : elog(ERROR, "could not find block containing chunk %p", chunk);
850 : :
1104 drowley@postgresql.o 851 : 0 : oldsize = block->endptr - (char *) pointer;
852 : : }
853 : : else
854 : : {
855 : 0 : block = MemoryChunkGetBlock(chunk);
856 : :
857 : : /*
858 : : * In this path, for speed reasons we just Assert that the referenced
859 : : * block is good. Future field experience may show that this Assert
860 : : * had better become a regular runtime test-and-elog check.
861 : : */
1044 peter@eisentraut.org 862 [ # # # # : 0 : Assert(GenerationBlockIsValid(block));
# # ]
863 : :
1104 drowley@postgresql.o 864 : 0 : oldsize = MemoryChunkGetValue(chunk);
865 : : }
866 : :
867 : 0 : set = block->context;
868 : :
869 : : #ifdef MEMORY_CONTEXT_CHECKING
870 : : /* Test for someone scribbling on unused space in chunk */
1095 871 [ # # ]: 0 : Assert(chunk->requested_size < oldsize);
872 [ # # ]: 0 : if (!sentinel_ok(pointer, chunk->requested_size))
873 [ # # ]: 0 : elog(WARNING, "detected write past chunk end in %s %p",
874 : : ((MemoryContext) set)->name, chunk);
875 : : #endif
876 : :
877 : : /*
878 : : * Maybe the allocated area already big enough. (In particular, we always
879 : : * fall out here if the requested size is a decrease.)
880 : : *
881 : : * This memory context does not use power-of-2 chunk sizing and instead
882 : : * carves the chunks to be as small as possible, so most repalloc() calls
883 : : * will end up in the palloc/memcpy/pfree branch.
884 : : *
885 : : * XXX Perhaps we should annotate this condition with unlikely()?
886 : : */
887 : : #ifdef MEMORY_CONTEXT_CHECKING
888 : : /* With MEMORY_CONTEXT_CHECKING, we need an extra byte for the sentinel */
427 889 [ # # ]: 0 : if (oldsize > size)
890 : : #else
891 : : if (oldsize >= size)
892 : : #endif
893 : : {
894 : : #ifdef MEMORY_CONTEXT_CHECKING
2844 simon@2ndQuadrant.co 895 : 0 : Size oldrequest = chunk->requested_size;
896 : :
897 : : #ifdef RANDOMIZE_ALLOCATED_MEMORY
898 : : /* We can only fill the extra space if we know the prior request */
899 : : if (size > oldrequest)
900 : : randomize_mem((char *) pointer + oldrequest,
901 : : size - oldrequest);
902 : : #endif
903 : :
904 : 0 : chunk->requested_size = size;
905 : :
906 : : /*
907 : : * If this is an increase, mark any newly-available part UNDEFINED.
908 : : * Otherwise, mark the obsolete part NOACCESS.
909 : : */
910 : : if (size > oldrequest)
911 : : VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest,
912 : : size - oldrequest);
913 : : else
914 : : VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size,
915 : : oldsize - size);
916 : :
917 : : /* set mark to catch clobber of "unused" space */
1095 drowley@postgresql.o 918 : 0 : set_sentinel(pointer, size);
919 : : #else /* !MEMORY_CONTEXT_CHECKING */
920 : :
921 : : /*
922 : : * We don't have the information to determine whether we're growing
923 : : * the old request or shrinking it, so we conservatively mark the
924 : : * entire new allocation DEFINED.
925 : : */
926 : : VALGRIND_MAKE_MEM_NOACCESS(pointer, oldsize);
927 : : VALGRIND_MAKE_MEM_DEFINED(pointer, size);
928 : : #endif
929 : :
930 : : /* Disallow access to the chunk header. */
931 : : VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
932 : :
2844 simon@2ndQuadrant.co 933 : 0 : return pointer;
934 : : }
935 : :
936 : : /* allocate new chunk (this also checks size is valid) */
557 drowley@postgresql.o 937 : 0 : newPointer = GenerationAlloc((MemoryContext) set, size, flags);
938 : :
939 : : /* leave immediately if request was not completed */
2844 simon@2ndQuadrant.co 940 [ # # ]: 0 : if (newPointer == NULL)
941 : : {
942 : : /* Disallow access to the chunk header. */
943 : : VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
557 drowley@postgresql.o 944 : 0 : return MemoryContextAllocationFailure((MemoryContext) set, size, flags);
945 : : }
946 : :
947 : : /*
948 : : * GenerationAlloc() may have returned a region that is still NOACCESS.
949 : : * Change it to UNDEFINED for the moment; memcpy() will then transfer
950 : : * definedness from the old allocation to the new. If we know the old
951 : : * allocation, copy just that much. Otherwise, make the entire old chunk
952 : : * defined to avoid errors as we copy the currently-NOACCESS trailing
953 : : * bytes.
954 : : */
955 : : VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size);
956 : : #ifdef MEMORY_CONTEXT_CHECKING
2844 simon@2ndQuadrant.co 957 : 0 : oldsize = chunk->requested_size;
958 : : #else
959 : : VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
960 : : #endif
961 : :
962 : : /* transfer existing data (certain to fit) */
963 : 0 : memcpy(newPointer, pointer, oldsize);
964 : :
965 : : /* free old chunk */
1104 drowley@postgresql.o 966 : 0 : GenerationFree(pointer);
967 : :
2844 simon@2ndQuadrant.co 968 : 0 : return newPointer;
969 : : }
970 : :
971 : : /*
972 : : * GenerationGetChunkContext
973 : : * Return the MemoryContext that 'pointer' belongs to.
974 : : */
975 : : MemoryContext
1104 drowley@postgresql.o 976 : 0 : GenerationGetChunkContext(void *pointer)
977 : : {
978 : 0 : MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
979 : : GenerationBlock *block;
980 : :
981 : : /* Allow access to the chunk header. */
982 : : VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ);
983 : :
984 [ # # ]: 0 : if (MemoryChunkIsExternal(chunk))
985 : 0 : block = ExternalChunkGetBlock(chunk);
986 : : else
987 : 0 : block = (GenerationBlock *) MemoryChunkGetBlock(chunk);
988 : :
989 : : /* Disallow access to the chunk header. */
990 : : VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
991 : :
1044 peter@eisentraut.org 992 [ # # # # : 0 : Assert(GenerationBlockIsValid(block));
# # ]
1104 drowley@postgresql.o 993 : 0 : return &block->context->header;
994 : : }
995 : :
996 : : /*
997 : : * GenerationGetChunkSpace
998 : : * Given a currently-allocated chunk, determine the total space
999 : : * it occupies (including all memory-allocation overhead).
1000 : : */
1001 : : Size
1104 drowley@postgresql.o 1002 :CBC 16799554 : GenerationGetChunkSpace(void *pointer)
1003 : : {
1004 : 16799554 : MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
1005 : : Size chunksize;
1006 : :
1007 : : /* Allow access to the chunk header. */
1008 : : VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ);
1009 : :
1010 [ + + ]: 16799554 : if (MemoryChunkIsExternal(chunk))
1011 : : {
1012 : 88 : GenerationBlock *block = ExternalChunkGetBlock(chunk);
1013 : :
1044 peter@eisentraut.org 1014 [ + - + - : 88 : Assert(GenerationBlockIsValid(block));
- + ]
1104 drowley@postgresql.o 1015 : 88 : chunksize = block->endptr - (char *) pointer;
1016 : : }
1017 : : else
1018 : 16799466 : chunksize = MemoryChunkGetValue(chunk);
1019 : :
1020 : : /* Disallow access to the chunk header. */
1021 : : VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
1022 : :
1023 : 16799554 : return Generation_CHUNKHDRSZ + chunksize;
1024 : : }
1025 : :
1026 : : /*
1027 : : * GenerationIsEmpty
1028 : : * Is a GenerationContext empty of any allocated space?
1029 : : */
1030 : : bool
2844 simon@2ndQuadrant.co 1031 :UBC 0 : GenerationIsEmpty(MemoryContext context)
1032 : : {
2838 rhaas@postgresql.org 1033 : 0 : GenerationContext *set = (GenerationContext *) context;
1034 : : dlist_iter iter;
1035 : :
1044 peter@eisentraut.org 1036 [ # # # # ]: 0 : Assert(GenerationIsValid(set));
1037 : :
1251 drowley@postgresql.o 1038 [ # # # # ]: 0 : dlist_foreach(iter, &set->blocks)
1039 : : {
1040 : 0 : GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1041 : :
1042 [ # # ]: 0 : if (block->nchunks > 0)
1043 : 0 : return false;
1044 : : }
1045 : :
1046 : 0 : return true;
1047 : : }
1048 : :
1049 : : /*
1050 : : * GenerationStats
1051 : : * Compute stats about memory consumption of a Generation context.
1052 : : *
1053 : : * printfunc: if not NULL, pass a human-readable stats string to this.
1054 : : * passthru: pass this pointer through to printfunc.
1055 : : * totals: if not NULL, add stats about this context into *totals.
1056 : : * print_to_stderr: print stats to stderr if true, elog otherwise.
1057 : : *
1058 : : * XXX freespace only accounts for empty space at the end of the block, not
1059 : : * space of freed chunks (which is unknown).
1060 : : */
1061 : : void
2720 tgl@sss.pgh.pa.us 1062 :CBC 15 : GenerationStats(MemoryContext context,
1063 : : MemoryStatsPrintFunc printfunc, void *passthru,
1064 : : MemoryContextCounters *totals, bool print_to_stderr)
1065 : : {
2838 rhaas@postgresql.org 1066 : 15 : GenerationContext *set = (GenerationContext *) context;
2844 simon@2ndQuadrant.co 1067 : 15 : Size nblocks = 0;
1068 : 15 : Size nchunks = 0;
1069 : 15 : Size nfreechunks = 0;
1070 : : Size totalspace;
1071 : 15 : Size freespace = 0;
1072 : : dlist_iter iter;
1073 : :
1044 peter@eisentraut.org 1074 [ + - - + ]: 15 : Assert(GenerationIsValid(set));
1075 : :
1076 : : /* Include context header in totalspace */
2720 tgl@sss.pgh.pa.us 1077 : 15 : totalspace = MAXALIGN(sizeof(GenerationContext));
1078 : :
2844 simon@2ndQuadrant.co 1079 [ + - + + ]: 54 : dlist_foreach(iter, &set->blocks)
1080 : : {
1081 : 39 : GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1082 : :
1083 : 39 : nblocks++;
1084 : 39 : nchunks += block->nchunks;
1085 : 39 : nfreechunks += block->nfree;
2843 tgl@sss.pgh.pa.us 1086 : 39 : totalspace += block->blksize;
2844 simon@2ndQuadrant.co 1087 : 39 : freespace += (block->endptr - block->freeptr);
1088 : : }
1089 : :
2720 tgl@sss.pgh.pa.us 1090 [ - + ]: 15 : if (printfunc)
1091 : : {
1092 : : char stats_string[200];
1093 : :
2720 tgl@sss.pgh.pa.us 1094 :UBC 0 : snprintf(stats_string, sizeof(stats_string),
1095 : : "%zu total in %zu blocks (%zu chunks); %zu free (%zu chunks); %zu used",
1096 : : totalspace, nblocks, nchunks, freespace,
1097 : : nfreechunks, totalspace - freespace);
1614 fujii@postgresql.org 1098 : 0 : printfunc(context, passthru, stats_string, print_to_stderr);
1099 : : }
1100 : :
2844 simon@2ndQuadrant.co 1101 [ + - ]:CBC 15 : if (totals)
1102 : : {
1103 : 15 : totals->nblocks += nblocks;
1104 : 15 : totals->freechunks += nfreechunks;
1105 : 15 : totals->totalspace += totalspace;
1106 : 15 : totals->freespace += freespace;
1107 : : }
1108 : 15 : }
1109 : :
1110 : :
1111 : : #ifdef MEMORY_CONTEXT_CHECKING
1112 : :
1113 : : /*
1114 : : * GenerationCheck
1115 : : * Walk through chunks and check consistency of memory.
1116 : : *
1117 : : * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll
1118 : : * find yourself in an infinite loop when trouble occurs, because this
1119 : : * routine will be entered again when elog cleanup tries to release memory!
1120 : : */
1121 : : void
1122 : 121861 : GenerationCheck(MemoryContext context)
1123 : : {
2838 rhaas@postgresql.org 1124 : 121861 : GenerationContext *gen = (GenerationContext *) context;
2824 tgl@sss.pgh.pa.us 1125 : 121861 : const char *name = context->name;
1126 : : dlist_iter iter;
2164 tomas.vondra@postgre 1127 : 121861 : Size total_allocated = 0;
1128 : :
1129 : : /* walk all blocks in this context */
2844 simon@2ndQuadrant.co 1130 [ + - + + ]: 256042 : dlist_foreach(iter, &gen->blocks)
1131 : : {
2843 tgl@sss.pgh.pa.us 1132 : 134181 : GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1133 : : int nfree,
1134 : : nchunks;
1135 : : char *ptr;
1104 drowley@postgresql.o 1136 : 134181 : bool has_external_chunk = false;
1137 : :
2167 tomas.vondra@postgre 1138 : 134181 : total_allocated += block->blksize;
1139 : :
1140 : : /*
1141 : : * nfree > nchunks is surely wrong. Equality is allowed as the block
1142 : : * might completely empty if it's the freeblock.
1143 : : */
1251 drowley@postgresql.o 1144 [ - + ]: 134181 : if (block->nfree > block->nchunks)
2844 simon@2ndQuadrant.co 1145 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
1146 : : name, block->nfree, block, block->nchunks);
1147 : :
1148 : : /* check block belongs to the correct context */
1104 drowley@postgresql.o 1149 [ - + ]:CBC 134181 : if (block->context != gen)
1104 drowley@postgresql.o 1150 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: bogus context link in block %p",
1151 : : name, block);
1152 : :
1153 : : /* Now walk through the chunks and count them. */
2844 simon@2ndQuadrant.co 1154 :CBC 134181 : nfree = 0;
1155 : 134181 : nchunks = 0;
1156 : 134181 : ptr = ((char *) block) + Generation_BLOCKHDRSZ;
1157 : :
1158 [ + + ]: 5886237 : while (ptr < block->freeptr)
1159 : : {
1104 drowley@postgresql.o 1160 : 5752056 : MemoryChunk *chunk = (MemoryChunk *) ptr;
1161 : : GenerationBlock *chunkblock;
1162 : : Size chunksize;
1163 : :
1164 : : /* Allow access to the chunk header. */
1165 : : VALGRIND_MAKE_MEM_DEFINED(chunk, Generation_CHUNKHDRSZ);
1166 : :
1167 [ + + ]: 5752056 : if (MemoryChunkIsExternal(chunk))
1168 : : {
1169 : 88 : chunkblock = ExternalChunkGetBlock(chunk);
1170 : 88 : chunksize = block->endptr - (char *) MemoryChunkGetPointer(chunk);
1171 : 88 : has_external_chunk = true;
1172 : : }
1173 : : else
1174 : : {
1175 : 5751968 : chunkblock = MemoryChunkGetBlock(chunk);
1176 : 5751968 : chunksize = MemoryChunkGetValue(chunk);
1177 : : }
1178 : :
1179 : : /* move to the next chunk */
1180 : 5752056 : ptr += (chunksize + Generation_CHUNKHDRSZ);
1181 : :
2843 tgl@sss.pgh.pa.us 1182 : 5752056 : nchunks += 1;
1183 : :
1184 : : /* chunks have both block and context pointers, so check both */
1104 drowley@postgresql.o 1185 [ - + ]: 5752056 : if (chunkblock != block)
2844 simon@2ndQuadrant.co 1186 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p",
1187 : : name, block, chunk);
1188 : :
1189 : :
1190 : : /* is chunk allocated? */
1104 drowley@postgresql.o 1191 [ + + ]:CBC 5752056 : if (chunk->requested_size != InvalidAllocSize)
1192 : : {
1193 : : /* now make sure the chunk size is correct */
1194 [ + - ]: 5735358 : if (chunksize < chunk->requested_size ||
1195 [ - + ]: 5735358 : chunksize != MAXALIGN(chunksize))
1104 drowley@postgresql.o 1196 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
1197 : : name, block, chunk);
1198 : :
1199 : : /* check sentinel */
1095 drowley@postgresql.o 1200 [ - + ]:CBC 5735358 : Assert(chunk->requested_size < chunksize);
1201 [ - + ]: 5735358 : if (!sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size))
2844 simon@2ndQuadrant.co 1202 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p",
1203 : : name, block, chunk);
1204 : : }
1205 : : else
2844 simon@2ndQuadrant.co 1206 :CBC 16698 : nfree += 1;
1207 : :
1208 : : /* if chunk is allocated, disallow access to the chunk header */
1104 drowley@postgresql.o 1209 : 5752056 : if (chunk->requested_size != InvalidAllocSize)
1210 : : VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
1211 : : }
1212 : :
1213 : : /*
1214 : : * Make sure we got the expected number of allocated and free chunks
1215 : : * (as tracked in the block header).
1216 : : */
2844 simon@2ndQuadrant.co 1217 [ - + ]: 134181 : if (nchunks != block->nchunks)
2844 simon@2ndQuadrant.co 1218 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d",
1219 : : name, nchunks, block, block->nchunks);
1220 : :
2844 simon@2ndQuadrant.co 1221 [ - + ]:CBC 134181 : if (nfree != block->nfree)
2844 simon@2ndQuadrant.co 1222 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
1223 : : name, nfree, block, block->nfree);
1224 : :
1104 drowley@postgresql.o 1225 [ + + - + ]:CBC 134181 : if (has_external_chunk && nchunks > 1)
1104 drowley@postgresql.o 1226 [ # # ]:UBC 0 : elog(WARNING, "problem in Generation %s: external chunk on non-dedicated block %p",
1227 : : name, block);
1228 : :
1229 : : }
1230 : :
1997 jdavis@postgresql.or 1231 [ - + ]:CBC 121861 : Assert(total_allocated == context->mem_allocated);
2844 simon@2ndQuadrant.co 1232 : 121861 : }
1233 : :
1234 : : #endif /* MEMORY_CONTEXT_CHECKING */
|