PostgreSQL Source Code git master
Loading...
Searching...
No Matches
generation.c
Go to the documentation of this file.
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-2026, 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"
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
53typedef struct GenerationBlock GenerationBlock; /* forward reference */
54
55typedef 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 */
61typedef 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 */
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 */
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 ((set) && IsA(set, GenerationContext))
106
107/*
108 * GenerationBlockIsValid
109 * True iff block is valid block of generation set.
110 */
111#define GenerationBlockIsValid(block) \
112 ((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 */
137static inline void GenerationBlockInit(GenerationContext *context,
138 GenerationBlock *block,
139 Size blksize);
140static inline void GenerationBlockMarkEmpty(GenerationBlock *block);
141static inline Size GenerationBlockFreeBytes(GenerationBlock *block);
142static 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 */
163 const char *name,
165 Size initBlockSize,
166 Size maxBlockSize)
167{
171 GenerationBlock *block;
172
173 /* ensure MemoryChunk's size is properly maxaligned */
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 */
188 Assert(initBlockSize == MAXALIGN(initBlockSize) &&
189 initBlockSize >= 1024);
190 Assert(maxBlockSize == MAXALIGN(maxBlockSize) &&
191 maxBlockSize >= initBlockSize &&
192 AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
193 Assert(minContextSize == 0 ||
195 minContextSize >= 1024 &&
196 minContextSize <= maxBlockSize));
197 Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
198
199 /* Determine size of initial block */
202 if (minContextSize != 0)
204 else
205 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 */
212 if (set == NULL)
213 {
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 */
231
232 dlist_init(&set->blocks);
233
234 /* Fill in the initial block's block header */
235 block = KeeperBlock(set);
236 /* determine the block size and initialize it */
239
240 /* add it to the doubly-linked list of blocks */
241 dlist_push_head(&set->blocks, &block->node);
242
243 /* use it as the current allocation block */
244 set->block = block;
245
246 /* No free block, yet */
247 set->freeblock = NULL;
248
249 /* Fill in GenerationContext-specific header fields */
250 set->initBlockSize = (uint32) initBlockSize;
251 set->maxBlockSize = (uint32) maxBlockSize;
252 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 */
264 set->allocChunkLimit = Min(maxBlockSize, MEMORYCHUNK_MAX_VALUE);
265 while ((Size) (set->allocChunkLimit + Generation_CHUNKHDRSZ) >
267 set->allocChunkLimit >>= 1;
268
269 /* Finally, do the type-independent part of context creation */
273 parent,
274 name);
275
276 ((MemoryContext) set)->mem_allocated = firstBlockSize;
277
278 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 */
290void
292{
293 GenerationContext *set = (GenerationContext *) context;
295
297
298#ifdef MEMORY_CONTEXT_CHECKING
299 /* Check for corruption and leaks before freeing */
300 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 */
308 set->freeblock = NULL;
309
311 {
313
314 if (IsKeeperBlock(set, block))
316 else
317 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 */
327
328 /* set it so new allocations to make use of the keeper block */
329 set->block = KeeperBlock(set);
330
331 /* Reset block size allocation sequence, too */
332 set->nextBlockSize = set->initBlockSize;
333
334 /* Ensure there is only 1 item in the dlist */
337}
338
339/*
340 * GenerationDelete
341 * Free all memory which is allocated in the given context.
342 */
343void
345{
346 /* Reset to release all releasable GenerationBlocks */
347 GenerationReset(context);
348
349 /* Destroy the vpool -- see notes in aset.c */
351
352 /* And free the context header and keeper block */
353 free(context);
354}
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 */
362static void *
363GenerationAllocLarge(MemoryContext context, Size size, int flags)
364{
365 GenerationContext *set = (GenerationContext *) context;
366 GenerationBlock *block;
367 MemoryChunk *chunk;
368 Size chunk_size;
370 Size blksize;
371
372 /* validate 'size' is within the limits for the given 'flags' */
373 MemoryContextCheckSize(context, size, flags);
374
375#ifdef MEMORY_CONTEXT_CHECKING
376 /* ensure there's always space for the sentinel byte */
377 chunk_size = MAXALIGN(size + 1);
378#else
379 chunk_size = MAXALIGN(size);
380#endif
383
384 block = (GenerationBlock *) malloc(blksize);
385 if (block == NULL)
386 return MemoryContextAllocationFailure(context, size, flags);
387
388 /* Make a vchunk covering the new block's header */
390
391 context->mem_allocated += blksize;
392
393 /* block with a single (used) chunk */
394 block->context = set;
395 block->blksize = blksize;
396 block->nchunks = 1;
397 block->nfree = 0;
398
399 /* the block is completely full */
400 block->freeptr = block->endptr = ((char *) block) + blksize;
401
402 chunk = (MemoryChunk *) (((char *) block) + Generation_BLOCKHDRSZ);
403
404 /* mark the MemoryChunk as externally managed */
406
407#ifdef MEMORY_CONTEXT_CHECKING
408 chunk->requested_size = size;
409 /* set mark to catch clobber of "unused" space */
410 Assert(size < 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 dlist_push_head(&set->blocks, &block->node);
420
421 /* Ensure any padding bytes are marked NOACCESS. */
423 chunk_size - size);
424
425 /* Disallow access to the chunk header. */
427
428 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 */
435static inline void *
437 Size size, Size chunk_size)
438{
439 MemoryChunk *chunk = (MemoryChunk *) (block->freeptr);
440
441 /* validate we've been given a block with enough free space */
442 Assert(block != NULL);
443 Assert((block->endptr - block->freeptr) >=
444 Generation_CHUNKHDRSZ + chunk_size);
445
446 /* Prepare to initialize the chunk header. */
448
449 block->nchunks += 1;
450 block->freeptr += (Generation_CHUNKHDRSZ + chunk_size);
451
452 Assert(block->freeptr <= block->endptr);
453
454 MemoryChunkSetHdrMask(chunk, block, chunk_size, MCTX_GENERATION_ID);
455#ifdef MEMORY_CONTEXT_CHECKING
456 chunk->requested_size = size;
457 /* set mark to catch clobber of "unused" space */
458 Assert(size < 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. */
468 chunk_size - size);
469
470 /* Disallow access to the chunk header. */
472
473 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 */
483static void *
485 Size chunk_size)
486{
487 GenerationContext *set = (GenerationContext *) context;
488 GenerationBlock *block;
489 Size blksize;
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 blksize = set->nextBlockSize;
497 set->nextBlockSize <<= 1;
498 if (set->nextBlockSize > set->maxBlockSize)
499 set->nextBlockSize = set->maxBlockSize;
500
501 /* we'll need space for the chunk, chunk hdr and block hdr */
503
504 /* round the size up to the next power of 2 */
505 if (blksize < required_size)
507
508 block = (GenerationBlock *) malloc(blksize);
509
510 if (block == NULL)
511 return MemoryContextAllocationFailure(context, size, flags);
512
513 /* Make a vchunk covering the new block's header */
515
516 context->mem_allocated += blksize;
517
518 /* initialize the new block */
519 GenerationBlockInit(set, block, blksize);
520
521 /* add it to the doubly-linked list of blocks */
522 dlist_push_head(&set->blocks, &block->node);
523
524 /* make this the current block */
525 set->block = block;
526
527 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 */
552void *
553GenerationAlloc(MemoryContext context, Size size, int flags)
554{
555 GenerationContext *set = (GenerationContext *) context;
556 GenerationBlock *block;
557 Size chunk_size;
559
561
562#ifdef MEMORY_CONTEXT_CHECKING
563 /* ensure there's always space for the sentinel byte */
564 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 if (chunk_size > set->allocChunkLimit)
574 return GenerationAllocLarge(context, size, flags);
575
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 block = set->block;
594
596 {
597 GenerationBlock *freeblock = set->freeblock;
598
599 /* freeblock, if set, must be empty */
600 Assert(freeblock == NULL || GenerationBlockIsEmpty(freeblock));
601
602 /* check if we have a freeblock and if it's big enough */
603 if (freeblock != NULL &&
605 {
606 /* make the freeblock the current block */
607 set->freeblock = NULL;
608 set->block = freeblock;
609
610 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 return GenerationAllocFromNewBlock(context, size, flags, chunk_size);
622 }
623 }
624
625 /* The current block has space, so just allocate chunk there. */
626 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 */
634static inline void
636 Size blksize)
637{
638 block->context = context;
639 block->blksize = blksize;
640 block->nchunks = 0;
641 block->nfree = 0;
642
643 block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
644 block->endptr = ((char *) block) + blksize;
645
646 /* Mark unallocated space NOACCESS. */
648 blksize - Generation_BLOCKHDRSZ);
649}
650
651/*
652 * GenerationBlockMarkEmpty
653 * Set a block as empty. Does not free the block.
654 */
655static inline void
657{
658#if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
659 char *datastart = ((char *) block) + Generation_BLOCKHDRSZ;
660#endif
661
662#ifdef CLOBBER_FREED_MEMORY
664#else
665 /* wipe_mem() would have done this */
667#endif
668
669 /* Reset the block, but don't return it to malloc */
670 block->nchunks = 0;
671 block->nfree = 0;
672 block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
673}
674
675/*
676 * GenerationBlockFreeBytes
677 * Returns the number of bytes free in 'block'
678 */
679static inline Size
681{
682 return (block->endptr - block->freeptr);
683}
684
685/*
686 * GenerationBlockFree
687 * Remove 'block' from 'set' and release the memory consumed by it.
688 */
689static inline void
691{
692 /* Make sure nobody tries to free the keeper block */
693 Assert(!IsKeeperBlock(set, block));
694 /* We shouldn't be freeing the freeblock either */
695 Assert(block != set->freeblock);
696
697 /* release the block from the list of blocks */
698 dlist_delete(&block->node);
699
700 ((MemoryContext) set)->mem_allocated -= block->blksize;
701
702#ifdef CLOBBER_FREED_MEMORY
703 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 free(block);
710}
711
712/*
713 * GenerationFree
714 * Update number of chunks in the block, and consider freeing the block
715 * if it's become empty.
716 */
717void
718GenerationFree(void *pointer)
719{
720 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
721 GenerationBlock *block;
723#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
724 || defined(CLOBBER_FREED_MEMORY)
726#endif
727
728 /* Allow access to the chunk header. */
730
731 if (MemoryChunkIsExternal(chunk))
732 {
733 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 */
739 if (!GenerationBlockIsValid(block))
740 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)
744 chunksize = block->endptr - (char *) pointer;
745#endif
746 }
747 else
748 {
749 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 */
757
758#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
759 || defined(CLOBBER_FREED_MEMORY)
761#endif
762 }
763
764#ifdef MEMORY_CONTEXT_CHECKING
765 /* See comments in AllocSetFree about uses of ERROR and WARNING here */
766 /* Test for previously-freed chunk */
767 if (unlikely(chunk->requested_size == InvalidAllocSize))
768 elog(ERROR, "detected double pfree in %s %p",
769 ((MemoryContext) block->context)->name, chunk);
770 /* Test for someone scribbling on unused space in chunk */
771 Assert(chunk->requested_size < chunksize);
772 if (!sentinel_ok(pointer, chunk->requested_size))
773 elog(WARNING, "detected write past chunk end in %s %p",
774 ((MemoryContext) block->context)->name, chunk);
775#endif
776
777#ifdef CLOBBER_FREED_MEMORY
778 wipe_mem(pointer, chunksize);
779#endif
780
781#ifdef MEMORY_CONTEXT_CHECKING
782 /* Reset requested_size to InvalidAllocSize in freed chunks */
783 chunk->requested_size = InvalidAllocSize;
784#endif
785
786 block->nfree += 1;
787
788 Assert(block->nchunks > 0);
789 Assert(block->nfree <= block->nchunks);
790 Assert(block != block->context->freeblock);
791
792 /* If there are still allocated chunks in the block, we're done. */
793 if (likely(block->nfree < block->nchunks))
794 return;
795
796 set = block->context;
797
798 /*-----------------------
799 * The block this allocation was on has now become completely empty of
800 * chunks. In the general case, we can now return the memory for this
801 * block back to malloc. However, there are cases where we don't want to
802 * do that:
803 *
804 * 1) If it's the keeper block. This block was malloc'd in the same
805 * allocation as the context itself and can't be free'd without
806 * freeing the context.
807 * 2) If it's the current block. We could free this, but doing so would
808 * leave us nothing to set the current block to, so we just mark the
809 * block as empty so new allocations can reuse it again.
810 * 3) If we have no "freeblock" set, then we save a single block for
811 * future allocations to avoid having to malloc a new block again.
812 * This is useful for FIFO workloads as it avoids continual
813 * free/malloc cycles.
814 */
815 if (IsKeeperBlock(set, block) || set->block == block)
816 GenerationBlockMarkEmpty(block); /* case 1 and 2 */
817 else if (set->freeblock == NULL)
818 {
819 /* case 3 */
821 set->freeblock = block;
822 }
823 else
824 GenerationBlockFree(set, block); /* Otherwise, free it */
825}
826
827/*
828 * GenerationRealloc
829 * When handling repalloc, we simply allocate a new chunk, copy the data
830 * and discard the old one. The only exception is when the new size fits
831 * into the old chunk - in that case we just update chunk header.
832 */
833void *
834GenerationRealloc(void *pointer, Size size, int flags)
835{
836 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
838 GenerationBlock *block;
841
842 /* Allow access to the chunk header. */
844
845 if (MemoryChunkIsExternal(chunk))
846 {
847 block = ExternalChunkGetBlock(chunk);
848
849 /*
850 * Try to verify that we have a sane block pointer: the block header
851 * should reference a generation context.
852 */
853 if (!GenerationBlockIsValid(block))
854 elog(ERROR, "could not find block containing chunk %p", chunk);
855
856 oldsize = block->endptr - (char *) pointer;
857 }
858 else
859 {
860 block = MemoryChunkGetBlock(chunk);
861
862 /*
863 * In this path, for speed reasons we just Assert that the referenced
864 * block is good. Future field experience may show that this Assert
865 * had better become a regular runtime test-and-elog check.
866 */
868
870 }
871
872 set = block->context;
873
874#ifdef MEMORY_CONTEXT_CHECKING
875 /* See comments in AllocSetFree about uses of ERROR and WARNING here */
876 /* Test for previously-freed chunk */
877 if (unlikely(chunk->requested_size == InvalidAllocSize))
878 elog(ERROR, "detected realloc of freed chunk in %s %p",
879 ((MemoryContext) set)->name, chunk);
880 /* Test for someone scribbling on unused space in chunk */
881 Assert(chunk->requested_size < oldsize);
882 if (!sentinel_ok(pointer, chunk->requested_size))
883 elog(WARNING, "detected write past chunk end in %s %p",
884 ((MemoryContext) set)->name, chunk);
885#endif
886
887 /*
888 * Maybe the allocated area already big enough. (In particular, we always
889 * fall out here if the requested size is a decrease.)
890 *
891 * This memory context does not use power-of-2 chunk sizing and instead
892 * carves the chunks to be as small as possible, so most repalloc() calls
893 * will end up in the palloc/memcpy/pfree branch.
894 *
895 * XXX Perhaps we should annotate this condition with unlikely()?
896 */
897#ifdef MEMORY_CONTEXT_CHECKING
898 /* With MEMORY_CONTEXT_CHECKING, we need an extra byte for the sentinel */
899 if (oldsize > size)
900#else
901 if (oldsize >= size)
902#endif
903 {
904#ifdef MEMORY_CONTEXT_CHECKING
905 Size oldrequest = chunk->requested_size;
906
907#ifdef RANDOMIZE_ALLOCATED_MEMORY
908 /* We can only fill the extra space if we know the prior request */
909 if (size > oldrequest)
910 randomize_mem((char *) pointer + oldrequest,
911 size - oldrequest);
912#endif
913
914 chunk->requested_size = size;
915
916 /*
917 * If this is an increase, mark any newly-available part UNDEFINED.
918 * Otherwise, mark the obsolete part NOACCESS.
919 */
920 if (size > oldrequest)
921 VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest,
922 size - oldrequest);
923 else
924 VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size,
925 oldsize - size);
926
927 /* set mark to catch clobber of "unused" space */
928 set_sentinel(pointer, size);
929#else /* !MEMORY_CONTEXT_CHECKING */
930
931 /*
932 * We don't have the information to determine whether we're growing
933 * the old request or shrinking it, so we conservatively mark the
934 * entire new allocation DEFINED.
935 */
937 VALGRIND_MAKE_MEM_DEFINED(pointer, size);
938#endif
939
940 /* Disallow access to the chunk header. */
942
943 return pointer;
944 }
945
946 /* allocate new chunk (this also checks size is valid) */
947 newPointer = GenerationAlloc((MemoryContext) set, size, flags);
948
949 /* leave immediately if request was not completed */
950 if (newPointer == NULL)
951 {
952 /* Disallow access to the chunk header. */
954 return MemoryContextAllocationFailure((MemoryContext) set, size, flags);
955 }
956
957 /*
958 * GenerationAlloc() may have returned a region that is still NOACCESS.
959 * Change it to UNDEFINED for the moment; memcpy() will then transfer
960 * definedness from the old allocation to the new. If we know the old
961 * allocation, copy just that much. Otherwise, make the entire old chunk
962 * defined to avoid errors as we copy the currently-NOACCESS trailing
963 * bytes.
964 */
966#ifdef MEMORY_CONTEXT_CHECKING
967 oldsize = chunk->requested_size;
968#else
970#endif
971
972 /* transfer existing data (certain to fit) */
973 memcpy(newPointer, pointer, oldsize);
974
975 /* free old chunk */
976 GenerationFree(pointer);
977
978 return newPointer;
979}
980
981/*
982 * GenerationGetChunkContext
983 * Return the MemoryContext that 'pointer' belongs to.
984 */
987{
988 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
989 GenerationBlock *block;
990
991 /* Allow access to the chunk header. */
993
994 if (MemoryChunkIsExternal(chunk))
995 block = ExternalChunkGetBlock(chunk);
996 else
997 block = (GenerationBlock *) MemoryChunkGetBlock(chunk);
998
999 /* Disallow access to the chunk header. */
1001
1003 return &block->context->header;
1004}
1005
1006/*
1007 * GenerationGetChunkSpace
1008 * Given a currently-allocated chunk, determine the total space
1009 * it occupies (including all memory-allocation overhead).
1010 */
1011Size
1013{
1014 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
1016
1017 /* Allow access to the chunk header. */
1019
1020 if (MemoryChunkIsExternal(chunk))
1021 {
1022 GenerationBlock *block = ExternalChunkGetBlock(chunk);
1023
1025 chunksize = block->endptr - (char *) pointer;
1026 }
1027 else
1029
1030 /* Disallow access to the chunk header. */
1032
1034}
1035
1036/*
1037 * GenerationIsEmpty
1038 * Is a GenerationContext empty of any allocated space?
1039 */
1040bool
1042{
1043 GenerationContext *set = (GenerationContext *) context;
1044 dlist_iter iter;
1045
1047
1048 dlist_foreach(iter, &set->blocks)
1049 {
1050 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1051
1052 if (block->nchunks > 0)
1053 return false;
1054 }
1055
1056 return true;
1057}
1058
1059/*
1060 * GenerationStats
1061 * Compute stats about memory consumption of a Generation context.
1062 *
1063 * printfunc: if not NULL, pass a human-readable stats string to this.
1064 * passthru: pass this pointer through to printfunc.
1065 * totals: if not NULL, add stats about this context into *totals.
1066 * print_to_stderr: print stats to stderr if true, elog otherwise.
1067 *
1068 * XXX freespace only accounts for empty space at the end of the block, not
1069 * space of freed chunks (which is unknown).
1070 */
1071void
1075{
1076 GenerationContext *set = (GenerationContext *) context;
1077 Size nblocks = 0;
1078 Size nchunks = 0;
1079 Size nfreechunks = 0;
1080 Size totalspace;
1081 Size freespace = 0;
1082 dlist_iter iter;
1083
1085
1086 /* Include context header in totalspace */
1087 totalspace = MAXALIGN(sizeof(GenerationContext));
1088
1089 dlist_foreach(iter, &set->blocks)
1090 {
1091 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1092
1093 nblocks++;
1094 nchunks += block->nchunks;
1095 nfreechunks += block->nfree;
1096 totalspace += block->blksize;
1097 freespace += (block->endptr - block->freeptr);
1098 }
1099
1100 if (printfunc)
1101 {
1102 char stats_string[200];
1103
1105 "%zu total in %zu blocks (%zu chunks); %zu free (%zu chunks); %zu used",
1106 totalspace, nblocks, nchunks, freespace,
1107 nfreechunks, totalspace - freespace);
1109 }
1110
1111 if (totals)
1112 {
1113 totals->nblocks += nblocks;
1114 totals->freechunks += nfreechunks;
1115 totals->totalspace += totalspace;
1116 totals->freespace += freespace;
1117 }
1118}
1119
1120
1121#ifdef MEMORY_CONTEXT_CHECKING
1122
1123/*
1124 * GenerationCheck
1125 * Walk through chunks and check consistency of memory.
1126 *
1127 * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll
1128 * find yourself in an infinite loop when trouble occurs, because this
1129 * routine will be entered again when elog cleanup tries to release memory!
1130 */
1131void
1133{
1134 GenerationContext *gen = (GenerationContext *) context;
1135 const char *name = context->name;
1136 dlist_iter iter;
1138
1139 /* walk all blocks in this context */
1140 dlist_foreach(iter, &gen->blocks)
1141 {
1142 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1143 int nfree,
1144 nchunks;
1145 char *ptr;
1146 bool has_external_chunk = false;
1147
1148 total_allocated += block->blksize;
1149
1150 /*
1151 * nfree > nchunks is surely wrong. Equality is allowed as the block
1152 * might completely empty if it's the freeblock.
1153 */
1154 if (block->nfree > block->nchunks)
1155 elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
1156 name, block->nfree, block, block->nchunks);
1157
1158 /* check block belongs to the correct context */
1159 if (block->context != gen)
1160 elog(WARNING, "problem in Generation %s: bogus context link in block %p",
1161 name, block);
1162
1163 /* Now walk through the chunks and count them. */
1164 nfree = 0;
1165 nchunks = 0;
1166 ptr = ((char *) block) + Generation_BLOCKHDRSZ;
1167
1168 while (ptr < block->freeptr)
1169 {
1170 MemoryChunk *chunk = (MemoryChunk *) ptr;
1173
1174 /* Allow access to the chunk header. */
1176
1177 if (MemoryChunkIsExternal(chunk))
1178 {
1180 chunksize = block->endptr - (char *) MemoryChunkGetPointer(chunk);
1181 has_external_chunk = true;
1182 }
1183 else
1184 {
1187 }
1188
1189 /* move to the next chunk */
1191
1192 nchunks += 1;
1193
1194 /* chunks have both block and context pointers, so check both */
1195 if (chunkblock != block)
1196 elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p",
1197 name, block, chunk);
1198
1199
1200 /* is chunk allocated? */
1201 if (chunk->requested_size != InvalidAllocSize)
1202 {
1203 /* now make sure the chunk size is correct */
1206 elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
1207 name, block, chunk);
1208
1209 /* check sentinel */
1210 Assert(chunk->requested_size < chunksize);
1211 if (!sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size))
1212 elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p",
1213 name, block, chunk);
1214 }
1215 else
1216 nfree += 1;
1217
1218 /* if chunk is allocated, disallow access to the chunk header */
1219 if (chunk->requested_size != InvalidAllocSize)
1221 }
1222
1223 /*
1224 * Make sure we got the expected number of allocated and free chunks
1225 * (as tracked in the block header).
1226 */
1227 if (nchunks != block->nchunks)
1228 elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d",
1229 name, nchunks, block, block->nchunks);
1230
1231 if (nfree != block->nfree)
1232 elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
1233 name, nfree, block, block->nfree);
1234
1235 if (has_external_chunk && nchunks > 1)
1236 elog(WARNING, "problem in Generation %s: external chunk on non-dedicated block %p",
1237 name, block);
1238
1239 }
1240
1242}
1243
1244#endif /* MEMORY_CONTEXT_CHECKING */
#define pg_noinline
Definition c.h:321
#define Min(x, y)
Definition c.h:1091
#define likely(x)
Definition c.h:437
#define MAXALIGN(LEN)
Definition c.h:896
#define Max(x, y)
Definition c.h:1085
#define Assert(condition)
Definition c.h:943
#define unlikely(x)
Definition c.h:438
uint32_t uint32
Definition c.h:624
#define StaticAssertDecl(condition, errmessage)
Definition c.h:1008
size_t Size
Definition c.h:689
memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets))
int errcode(int sqlerrcode)
Definition elog.c:874
int errdetail(const char *fmt,...) pg_attribute_printf(1
#define WARNING
Definition elog.h:37
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
#define ereport(elevel,...)
Definition elog.h:152
void * GenerationRealloc(void *pointer, Size size, int flags)
Definition generation.c:834
static void GenerationBlockInit(GenerationContext *context, GenerationBlock *block, Size blksize)
Definition generation.c:635
static pg_noinline void * GenerationAllocLarge(MemoryContext context, Size size, int flags)
Definition generation.c:363
#define IsKeeperBlock(set, block)
Definition generation.c:134
#define Generation_CHUNK_FRACTION
Definition generation.c:51
void GenerationReset(MemoryContext context)
Definition generation.c:291
static Size GenerationBlockFreeBytes(GenerationBlock *block)
Definition generation.c:680
#define KeeperBlock(set)
Definition generation.c:129
static void GenerationBlockFree(GenerationContext *set, GenerationBlock *block)
Definition generation.c:690
void GenerationFree(void *pointer)
Definition generation.c:718
MemoryContext GenerationGetChunkContext(void *pointer)
Definition generation.c:986
Size GenerationGetChunkSpace(void *pointer)
void * GenerationPointer
Definition generation.c:55
#define Generation_CHUNKHDRSZ
Definition generation.c:47
static void * GenerationAllocChunkFromBlock(MemoryContext context, GenerationBlock *block, Size size, Size chunk_size)
Definition generation.c:436
static void GenerationBlockMarkEmpty(GenerationBlock *block)
Definition generation.c:656
#define GenerationBlockIsValid(block)
Definition generation.c:111
bool GenerationIsEmpty(MemoryContext context)
void GenerationStats(MemoryContext context, MemoryStatsPrintFunc printfunc, void *passthru, MemoryContextCounters *totals, bool print_to_stderr)
#define GenerationBlockIsEmpty(b)
Definition generation.c:118
#define FIRST_BLOCKHDRSZ
Definition generation.c:48
#define Generation_BLOCKHDRSZ
Definition generation.c:46
MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, Size minContextSize, Size initBlockSize, Size maxBlockSize)
Definition generation.c:162
static pg_noinline void * GenerationAllocFromNewBlock(MemoryContext context, Size size, int flags, Size chunk_size)
Definition generation.c:484
void GenerationDelete(MemoryContext context)
Definition generation.c:344
#define GenerationIsValid(set)
Definition generation.c:104
void * GenerationAlloc(MemoryContext context, Size size, int flags)
Definition generation.c:553
#define ExternalChunkGetBlock(chunk)
Definition generation.c:125
#define dlist_foreach(iter, lhead)
Definition ilist.h:623
static void dlist_init(dlist_head *head)
Definition ilist.h:314
static bool dlist_has_next(const dlist_head *head, const dlist_node *node)
Definition ilist.h:503
static void dlist_delete(dlist_node *node)
Definition ilist.h:405
static void dlist_push_head(dlist_head *head, dlist_node *node)
Definition ilist.h:347
static dlist_node * dlist_head_node(dlist_head *head)
Definition ilist.h:565
#define dlist_foreach_modify(iter, lhead)
Definition ilist.h:640
static bool dlist_is_empty(const dlist_head *head)
Definition ilist.h:336
#define dlist_container(type, membername, ptr)
Definition ilist.h:593
void MemoryContextCreate(MemoryContext node, NodeTag tag, MemoryContextMethodID method_id, MemoryContext parent, const char *name)
Definition mcxt.c:1149
MemoryContext TopMemoryContext
Definition mcxt.c:166
void MemoryContextStats(MemoryContext context)
Definition mcxt.c:863
void * MemoryContextAllocationFailure(MemoryContext context, Size size, int flags)
Definition mcxt.c:1198
#define VALGRIND_DESTROY_MEMPOOL(context)
Definition memdebug.h:25
#define VALGRIND_MAKE_MEM_DEFINED(addr, size)
Definition memdebug.h:26
#define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed)
Definition memdebug.h:24
#define VALGRIND_MEMPOOL_ALLOC(context, addr, size)
Definition memdebug.h:29
#define VALGRIND_MEMPOOL_TRIM(context, addr, size)
Definition memdebug.h:32
#define VALGRIND_MEMPOOL_FREE(context, addr)
Definition memdebug.h:30
#define VALGRIND_MAKE_MEM_NOACCESS(addr, size)
Definition memdebug.h:27
#define VALGRIND_MAKE_MEM_UNDEFINED(addr, size)
Definition memdebug.h:28
void(* MemoryStatsPrintFunc)(MemoryContext context, void *passthru, const char *stats_string, bool print_to_stderr)
Definition memnodes.h:54
#define AllocHugeSizeIsValid(size)
Definition memutils.h:49
#define InvalidAllocSize
Definition memutils.h:47
static void MemoryContextCheckSize(MemoryContext context, Size size, int flags)
@ MCTX_GENERATION_ID
#define MEMORYCHUNK_MAX_BLOCKOFFSET
#define MEMORYCHUNK_MAX_VALUE
static Size MemoryChunkGetValue(MemoryChunk *chunk)
#define MemoryChunkGetPointer(c)
static bool MemoryChunkIsExternal(MemoryChunk *chunk)
static void * MemoryChunkGetBlock(MemoryChunk *chunk)
static void MemoryChunkSetHdrMaskExternal(MemoryChunk *chunk, MemoryContextMethodID methodid)
#define PointerGetMemoryChunk(p)
static void MemoryChunkSetHdrMask(MemoryChunk *chunk, void *block, Size value, MemoryContextMethodID methodid)
static char * errmsg
struct MemoryContextData * MemoryContext
Definition palloc.h:36
#define pg_nextpower2_size_t
#define snprintf
Definition port.h:260
static int fb(int x)
#define free(a)
#define malloc(a)
dlist_node node
Definition generation.c:91
GenerationContext * context
Definition generation.c:92
MemoryContextData header
Definition generation.c:63
GenerationBlock * freeblock
Definition generation.c:72
dlist_head blocks
Definition generation.c:74
GenerationBlock * block
Definition generation.c:71
uint32 allocChunkLimit
Definition generation.c:69
const char * name
Definition memnodes.h:131
dlist_node * cur
Definition ilist.h:179
const char * name