PostgreSQL Source Code git master
Loading...
Searching...
No Matches
generation.c File Reference
#include "postgres.h"
#include "lib/ilist.h"
#include "port/pg_bitutils.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"
#include "utils/memutils_internal.h"
#include "utils/memutils_memorychunk.h"
Include dependency graph for generation.c:

Go to the source code of this file.

Data Structures

struct  GenerationContext
 
struct  GenerationBlock
 

Macros

#define Generation_BLOCKHDRSZ   MAXALIGN(sizeof(GenerationBlock))
 
#define Generation_CHUNKHDRSZ   sizeof(MemoryChunk)
 
#define FIRST_BLOCKHDRSZ
 
#define Generation_CHUNK_FRACTION   8
 
#define GenerationIsValid(set)    ((set) && IsA(set, GenerationContext))
 
#define GenerationBlockIsValid(block)    ((block) && GenerationIsValid((block)->context))
 
#define GenerationBlockIsEmpty(b)   ((b)->nchunks == 0)
 
#define ExternalChunkGetBlock(chunk)    (GenerationBlock *) ((char *) chunk - Generation_BLOCKHDRSZ)
 
#define KeeperBlock(set)
 
#define IsKeeperBlock(set, block)   ((block) == (KeeperBlock(set)))
 

Typedefs

typedef struct GenerationBlock GenerationBlock
 
typedef voidGenerationPointer
 
typedef struct GenerationContext GenerationContext
 

Functions

static void GenerationBlockInit (GenerationContext *context, GenerationBlock *block, Size blksize)
 
static void GenerationBlockMarkEmpty (GenerationBlock *block)
 
static Size GenerationBlockFreeBytes (GenerationBlock *block)
 
static void GenerationBlockFree (GenerationContext *set, GenerationBlock *block)
 
MemoryContext GenerationContextCreate (MemoryContext parent, const char *name, Size minContextSize, Size initBlockSize, Size maxBlockSize)
 
void GenerationReset (MemoryContext context)
 
void GenerationDelete (MemoryContext context)
 
static pg_noinline voidGenerationAllocLarge (MemoryContext context, Size size, int flags)
 
static voidGenerationAllocChunkFromBlock (MemoryContext context, GenerationBlock *block, Size size, Size chunk_size)
 
static pg_noinline voidGenerationAllocFromNewBlock (MemoryContext context, Size size, int flags, Size chunk_size)
 
voidGenerationAlloc (MemoryContext context, Size size, int flags)
 
void GenerationFree (void *pointer)
 
voidGenerationRealloc (void *pointer, Size size, int flags)
 
MemoryContext GenerationGetChunkContext (void *pointer)
 
Size GenerationGetChunkSpace (void *pointer)
 
bool GenerationIsEmpty (MemoryContext context)
 
void GenerationStats (MemoryContext context, MemoryStatsPrintFunc printfunc, void *passthru, MemoryContextCounters *totals, bool print_to_stderr)
 

Macro Definition Documentation

◆ ExternalChunkGetBlock

#define ExternalChunkGetBlock (   chunk)     (GenerationBlock *) ((char *) chunk - Generation_BLOCKHDRSZ)

Definition at line 125 of file generation.c.

166{
170 GenerationBlock *block;
171
172 /* ensure MemoryChunk's size is properly maxaligned */
174 "sizeof(MemoryChunk) is not maxaligned");
175
176 /*
177 * First, validate allocation parameters. Asserts seem sufficient because
178 * nobody varies their parameters at runtime. We somewhat arbitrarily
179 * enforce a minimum 1K block size. We restrict the maximum block size to
180 * MEMORYCHUNK_MAX_BLOCKOFFSET as MemoryChunks are limited to this in
181 * regards to addressing the offset between the chunk and the block that
182 * the chunk is stored on. We would be unable to store the offset between
183 * the chunk and block for any chunks that were beyond
184 * MEMORYCHUNK_MAX_BLOCKOFFSET bytes into the block if the block was to be
185 * larger than this.
186 */
187 Assert(initBlockSize == MAXALIGN(initBlockSize) &&
188 initBlockSize >= 1024);
189 Assert(maxBlockSize == MAXALIGN(maxBlockSize) &&
190 maxBlockSize >= initBlockSize &&
191 AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
192 Assert(minContextSize == 0 ||
194 minContextSize >= 1024 &&
195 minContextSize <= maxBlockSize));
196 Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
197
198 /* Determine size of initial block */
201 if (minContextSize != 0)
203 else
204 allocSize = Max(allocSize, initBlockSize);
205
206 /*
207 * Allocate the initial block. Unlike other generation.c blocks, it
208 * starts with the context header and its block header follows that.
209 */
211 if (set == NULL)
212 {
216 errmsg("out of memory"),
217 errdetail("Failed while creating memory context \"%s\".",
218 name)));
219 }
220
221 /*
222 * Avoid writing code that can fail between here and MemoryContextCreate;
223 * we'd leak the header if we ereport in this stretch.
224 */
225
226 /* See comments about Valgrind interactions in aset.c */
227 VALGRIND_CREATE_MEMPOOL(set, 0, false);
228 /* This vchunk covers the GenerationContext and the keeper block header */
230
231 dlist_init(&set->blocks);
232
233 /* Fill in the initial block's block header */
234 block = KeeperBlock(set);
235 /* determine the block size and initialize it */
238
239 /* add it to the doubly-linked list of blocks */
240 dlist_push_head(&set->blocks, &block->node);
241
242 /* use it as the current allocation block */
243 set->block = block;
244
245 /* No free block, yet */
246 set->freeblock = NULL;
247
248 /* Fill in GenerationContext-specific header fields */
249 set->initBlockSize = (uint32) initBlockSize;
250 set->maxBlockSize = (uint32) maxBlockSize;
251 set->nextBlockSize = (uint32) initBlockSize;
252
253 /*
254 * Compute the allocation chunk size limit for this context.
255 *
256 * Limit the maximum size a non-dedicated chunk can be so that we can fit
257 * at least Generation_CHUNK_FRACTION of chunks this big onto the maximum
258 * sized block. We must further limit this value so that it's no more
259 * than MEMORYCHUNK_MAX_VALUE. We're unable to have non-external chunks
260 * larger than that value as we store the chunk size in the MemoryChunk
261 * 'value' field in the call to MemoryChunkSetHdrMask().
262 */
263 set->allocChunkLimit = Min(maxBlockSize, MEMORYCHUNK_MAX_VALUE);
264 while ((Size) (set->allocChunkLimit + Generation_CHUNKHDRSZ) >
266 set->allocChunkLimit >>= 1;
267
268 /* Finally, do the type-independent part of context creation */
272 parent,
273 name);
274
275 ((MemoryContext) set)->mem_allocated = firstBlockSize;
276
277 return (MemoryContext) set;
278}
279
280/*
281 * GenerationReset
282 * Frees all memory which is allocated in the given set.
283 *
284 * The initial "keeper" block (which shares a malloc chunk with the context
285 * header) is not given back to the operating system though. In this way, we
286 * don't thrash malloc() when a context is repeatedly reset after small
287 * allocations.
288 */
289void
291{
292 GenerationContext *set = (GenerationContext *) context;
294
296
297#ifdef MEMORY_CONTEXT_CHECKING
298 /* Check for corruption and leaks before freeing */
299 GenerationCheck(context);
300#endif
301
302 /*
303 * NULLify the free block pointer. We must do this before calling
304 * GenerationBlockFree as that function never expects to free the
305 * freeblock.
306 */
307 set->freeblock = NULL;
308
309 dlist_foreach_modify(miter, &set->blocks)
310 {
312
313 if (IsKeeperBlock(set, block))
315 else
316 GenerationBlockFree(set, block);
317 }
318
319 /*
320 * Instruct Valgrind to throw away all the vchunks associated with this
321 * context, except for the one covering the GenerationContext and
322 * keeper-block header. This gets rid of the vchunks for whatever user
323 * data is getting discarded by the context reset.
324 */
326
327 /* set it so new allocations to make use of the keeper block */
328 set->block = KeeperBlock(set);
329
330 /* Reset block size allocation sequence, too */
331 set->nextBlockSize = set->initBlockSize;
332
333 /* Ensure there is only 1 item in the dlist */
334 Assert(!dlist_is_empty(&set->blocks));
335 Assert(!dlist_has_next(&set->blocks, dlist_head_node(&set->blocks)));
336}
337
338/*
339 * GenerationDelete
340 * Free all memory which is allocated in the given context.
341 */
342void
344{
345 /* Reset to release all releasable GenerationBlocks */
346 GenerationReset(context);
347
348 /* Destroy the vpool -- see notes in aset.c */
350
351 /* And free the context header and keeper block */
352 free(context);
353}
354
355/*
356 * Helper for GenerationAlloc() that allocates an entire block for the chunk.
357 *
358 * GenerationAlloc()'s comment explains why this is separate.
359 */
361static void *
362GenerationAllocLarge(MemoryContext context, Size size, int flags)
363{
364 GenerationContext *set = (GenerationContext *) context;
365 GenerationBlock *block;
367 Size chunk_size;
369 Size blksize;
370
371 /* validate 'size' is within the limits for the given 'flags' */
372 MemoryContextCheckSize(context, size, flags);
373
374#ifdef MEMORY_CONTEXT_CHECKING
375 /* ensure there's always space for the sentinel byte */
376 chunk_size = MAXALIGN(size + 1);
377#else
378 chunk_size = MAXALIGN(size);
379#endif
382
383 block = (GenerationBlock *) malloc(blksize);
384 if (block == NULL)
385 return MemoryContextAllocationFailure(context, size, flags);
386
387 /* Make a vchunk covering the new block's header */
389
390 context->mem_allocated += blksize;
391
392 /* block with a single (used) chunk */
393 block->context = set;
394 block->blksize = blksize;
395 block->nchunks = 1;
396 block->nfree = 0;
397
398 /* the block is completely full */
399 block->freeptr = block->endptr = ((char *) block) + blksize;
400
401 chunk = (MemoryChunk *) (((char *) block) + Generation_BLOCKHDRSZ);
402
403 /* mark the MemoryChunk as externally managed */
405
406#ifdef MEMORY_CONTEXT_CHECKING
407 chunk->requested_size = size;
408 /* set mark to catch clobber of "unused" space */
409 Assert(size < chunk_size);
411#endif
412#ifdef RANDOMIZE_ALLOCATED_MEMORY
413 /* fill the allocated space with junk */
415#endif
416
417 /* add the block to the list of allocated blocks */
418 dlist_push_head(&set->blocks, &block->node);
419
420 /* Ensure any padding bytes are marked NOACCESS. */
422 chunk_size - size);
423
424 /* Disallow access to the chunk header. */
426
428}
429
430/*
431 * Small helper for allocating a new chunk from a chunk, to avoid duplicating
432 * the code between GenerationAlloc() and GenerationAllocFromNewBlock().
433 */
434static inline void *
436 Size size, Size chunk_size)
437{
438 MemoryChunk *chunk = (MemoryChunk *) (block->freeptr);
439
440 /* validate we've been given a block with enough free space */
441 Assert(block != NULL);
442 Assert((block->endptr - block->freeptr) >=
443 Generation_CHUNKHDRSZ + chunk_size);
444
445 /* Prepare to initialize the chunk header. */
447
448 block->nchunks += 1;
449 block->freeptr += (Generation_CHUNKHDRSZ + chunk_size);
450
451 Assert(block->freeptr <= block->endptr);
452
454#ifdef MEMORY_CONTEXT_CHECKING
455 chunk->requested_size = size;
456 /* set mark to catch clobber of "unused" space */
457 Assert(size < chunk_size);
459#endif
460#ifdef RANDOMIZE_ALLOCATED_MEMORY
461 /* fill the allocated space with junk */
463#endif
464
465 /* Ensure any padding bytes are marked NOACCESS. */
467 chunk_size - size);
468
469 /* Disallow access to the chunk header. */
471
473}
474
475/*
476 * Helper for GenerationAlloc() that allocates a new block and returns a chunk
477 * allocated from it.
478 *
479 * GenerationAlloc()'s comment explains why this is separate.
480 */
482static void *
483GenerationAllocFromNewBlock(MemoryContext context, Size size, int flags,
484 Size chunk_size)
485{
486 GenerationContext *set = (GenerationContext *) context;
487 GenerationBlock *block;
488 Size blksize;
490
491 /*
492 * The first such block has size initBlockSize, and we double the space in
493 * each succeeding block, but not more than maxBlockSize.
494 */
495 blksize = set->nextBlockSize;
496 set->nextBlockSize <<= 1;
497 if (set->nextBlockSize > set->maxBlockSize)
498 set->nextBlockSize = set->maxBlockSize;
499
500 /* we'll need space for the chunk, chunk hdr and block hdr */
502
503 /* round the size up to the next power of 2 */
504 if (blksize < required_size)
506
507 block = (GenerationBlock *) malloc(blksize);
508
509 if (block == NULL)
510 return MemoryContextAllocationFailure(context, size, flags);
511
512 /* Make a vchunk covering the new block's header */
514
515 context->mem_allocated += blksize;
516
517 /* initialize the new block */
518 GenerationBlockInit(set, block, blksize);
519
520 /* add it to the doubly-linked list of blocks */
521 dlist_push_head(&set->blocks, &block->node);
522
523 /* make this the current block */
524 set->block = block;
525
526 return GenerationAllocChunkFromBlock(context, block, size, chunk_size);
527}
528
529/*
530 * GenerationAlloc
531 * Returns a pointer to allocated memory of given size or raises an ERROR
532 * on allocation failure, or returns NULL when flags contains
533 * MCXT_ALLOC_NO_OOM.
534 *
535 * No request may exceed:
536 * MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ
537 * All callers use a much-lower limit.
538 *
539 * Note: when using valgrind, it doesn't matter how the returned allocation
540 * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will
541 * return space that is marked NOACCESS - GenerationRealloc has to beware!
542 *
543 * This function should only contain the most common code paths. Everything
544 * else should be in pg_noinline helper functions, thus avoiding the overhead
545 * of creating a stack frame for the common cases. Allocating memory is often
546 * a bottleneck in many workloads, so avoiding stack frame setup is
547 * worthwhile. Helper functions should always directly return the newly
548 * allocated memory so that we can just return that address directly as a tail
549 * call.
550 */
551void *
552GenerationAlloc(MemoryContext context, Size size, int flags)
553{
554 GenerationContext *set = (GenerationContext *) context;
555 GenerationBlock *block;
556 Size chunk_size;
558
560
561#ifdef MEMORY_CONTEXT_CHECKING
562 /* ensure there's always space for the sentinel byte */
563 chunk_size = MAXALIGN(size + 1);
564#else
565 chunk_size = MAXALIGN(size);
566#endif
567
568 /*
569 * If requested size exceeds maximum for chunks we hand the request off to
570 * GenerationAllocLarge().
571 */
572 if (chunk_size > set->allocChunkLimit)
573 return GenerationAllocLarge(context, size, flags);
574
576
577 /*
578 * Not an oversized chunk. We try to first make use of the current block,
579 * but if there's not enough space in it, instead of allocating a new
580 * block, we look to see if the empty freeblock has enough space. We
581 * don't try reusing the keeper block. If it's become empty we'll reuse
582 * that again only if the context is reset.
583 *
584 * We only try reusing the freeblock if we've no space for this allocation
585 * on the current block. When a freeblock exists, we'll switch to it once
586 * the first time we can't fit an allocation in the current block. We
587 * avoid ping-ponging between the two as we need to be careful not to
588 * fragment differently sized consecutive allocations between several
589 * blocks. Going between the two could cause fragmentation for FIFO
590 * workloads, which generation is meant to be good at.
591 */
592 block = set->block;
593
595 {
596 GenerationBlock *freeblock = set->freeblock;
597
598 /* freeblock, if set, must be empty */
599 Assert(freeblock == NULL || GenerationBlockIsEmpty(freeblock));
600
601 /* check if we have a freeblock and if it's big enough */
602 if (freeblock != NULL &&
604 {
605 /* make the freeblock the current block */
606 set->freeblock = NULL;
607 set->block = freeblock;
608
609 return GenerationAllocChunkFromBlock(context,
610 freeblock,
611 size,
612 chunk_size);
613 }
614 else
615 {
616 /*
617 * No freeblock, or it's not big enough for this allocation. Make
618 * a new block.
619 */
620 return GenerationAllocFromNewBlock(context, size, flags, chunk_size);
621 }
622 }
623
624 /* The current block has space, so just allocate chunk there. */
625 return GenerationAllocChunkFromBlock(context, block, size, chunk_size);
626}
627
628/*
629 * GenerationBlockInit
630 * Initializes 'block' assuming 'blksize'. Does not update the context's
631 * mem_allocated field.
632 */
633static inline void
635 Size blksize)
636{
637 block->context = context;
638 block->blksize = blksize;
639 block->nchunks = 0;
640 block->nfree = 0;
641
642 block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
643 block->endptr = ((char *) block) + blksize;
644
645 /* Mark unallocated space NOACCESS. */
647 blksize - Generation_BLOCKHDRSZ);
648}
649
650/*
651 * GenerationBlockMarkEmpty
652 * Set a block as empty. Does not free the block.
653 */
654static inline void
656{
657#if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
658 char *datastart = ((char *) block) + Generation_BLOCKHDRSZ;
659#endif
660
661#ifdef CLOBBER_FREED_MEMORY
663#else
664 /* wipe_mem() would have done this */
666#endif
667
668 /* Reset the block, but don't return it to malloc */
669 block->nchunks = 0;
670 block->nfree = 0;
671 block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
672}
673
674/*
675 * GenerationBlockFreeBytes
676 * Returns the number of bytes free in 'block'
677 */
678static inline Size
680{
681 return (block->endptr - block->freeptr);
682}
683
684/*
685 * GenerationBlockFree
686 * Remove 'block' from 'set' and release the memory consumed by it.
687 */
688static inline void
690{
691 /* Make sure nobody tries to free the keeper block */
692 Assert(!IsKeeperBlock(set, block));
693 /* We shouldn't be freeing the freeblock either */
694 Assert(block != set->freeblock);
695
696 /* release the block from the list of blocks */
697 dlist_delete(&block->node);
698
699 ((MemoryContext) set)->mem_allocated -= block->blksize;
700
701#ifdef CLOBBER_FREED_MEMORY
702 wipe_mem(block, block->blksize);
703#endif
704
705 /* As in aset.c, free block-header vchunks explicitly */
706 VALGRIND_MEMPOOL_FREE(set, block);
707
708 free(block);
709}
710
711/*
712 * GenerationFree
713 * Update number of chunks in the block, and consider freeing the block
714 * if it's become empty.
715 */
716void
717GenerationFree(void *pointer)
718{
720 GenerationBlock *block;
722#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
723 || defined(CLOBBER_FREED_MEMORY)
725#endif
726
727 /* Allow access to the chunk header. */
729
731 {
733
734 /*
735 * Try to verify that we have a sane block pointer: the block header
736 * should reference a generation context.
737 */
738 if (!GenerationBlockIsValid(block))
739 elog(ERROR, "could not find block containing chunk %p", chunk);
740
741#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
742 || defined(CLOBBER_FREED_MEMORY)
743 chunksize = block->endptr - (char *) pointer;
744#endif
745 }
746 else
747 {
748 block = MemoryChunkGetBlock(chunk);
749
750 /*
751 * In this path, for speed reasons we just Assert that the referenced
752 * block is good. Future field experience may show that this Assert
753 * had better become a regular runtime test-and-elog check.
754 */
756
757#if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
758 || defined(CLOBBER_FREED_MEMORY)
760#endif
761 }
762
763#ifdef MEMORY_CONTEXT_CHECKING
764 /* Test for someone scribbling on unused space in chunk */
765 Assert(chunk->requested_size < chunksize);
766 if (!sentinel_ok(pointer, chunk->requested_size))
767 elog(WARNING, "detected write past chunk end in %s %p",
768 ((MemoryContext) block->context)->name, chunk);
769#endif
770
771#ifdef CLOBBER_FREED_MEMORY
772 wipe_mem(pointer, chunksize);
773#endif
774
775#ifdef MEMORY_CONTEXT_CHECKING
776 /* Reset requested_size to InvalidAllocSize in freed chunks */
777 chunk->requested_size = InvalidAllocSize;
778#endif
779
780 block->nfree += 1;
781
782 Assert(block->nchunks > 0);
783 Assert(block->nfree <= block->nchunks);
784 Assert(block != block->context->freeblock);
785
786 /* If there are still allocated chunks in the block, we're done. */
787 if (likely(block->nfree < block->nchunks))
788 return;
789
790 set = block->context;
791
792 /*-----------------------
793 * The block this allocation was on has now become completely empty of
794 * chunks. In the general case, we can now return the memory for this
795 * block back to malloc. However, there are cases where we don't want to
796 * do that:
797 *
798 * 1) If it's the keeper block. This block was malloc'd in the same
799 * allocation as the context itself and can't be free'd without
800 * freeing the context.
801 * 2) If it's the current block. We could free this, but doing so would
802 * leave us nothing to set the current block to, so we just mark the
803 * block as empty so new allocations can reuse it again.
804 * 3) If we have no "freeblock" set, then we save a single block for
805 * future allocations to avoid having to malloc a new block again.
806 * This is useful for FIFO workloads as it avoids continual
807 * free/malloc cycles.
808 */
809 if (IsKeeperBlock(set, block) || set->block == block)
810 GenerationBlockMarkEmpty(block); /* case 1 and 2 */
811 else if (set->freeblock == NULL)
812 {
813 /* case 3 */
815 set->freeblock = block;
816 }
817 else
818 GenerationBlockFree(set, block); /* Otherwise, free it */
819}
820
821/*
822 * GenerationRealloc
823 * When handling repalloc, we simply allocate a new chunk, copy the data
824 * and discard the old one. The only exception is when the new size fits
825 * into the old chunk - in that case we just update chunk header.
826 */
827void *
828GenerationRealloc(void *pointer, Size size, int flags)
829{
832 GenerationBlock *block;
835
836 /* Allow access to the chunk header. */
838
840 {
842
843 /*
844 * Try to verify that we have a sane block pointer: the block header
845 * should reference a generation context.
846 */
847 if (!GenerationBlockIsValid(block))
848 elog(ERROR, "could not find block containing chunk %p", chunk);
849
850 oldsize = block->endptr - (char *) pointer;
851 }
852 else
853 {
854 block = MemoryChunkGetBlock(chunk);
855
856 /*
857 * In this path, for speed reasons we just Assert that the referenced
858 * block is good. Future field experience may show that this Assert
859 * had better become a regular runtime test-and-elog check.
860 */
862
864 }
865
866 set = block->context;
867
868#ifdef MEMORY_CONTEXT_CHECKING
869 /* Test for someone scribbling on unused space in chunk */
870 Assert(chunk->requested_size < oldsize);
871 if (!sentinel_ok(pointer, chunk->requested_size))
872 elog(WARNING, "detected write past chunk end in %s %p",
873 ((MemoryContext) set)->name, chunk);
874#endif
875
876 /*
877 * Maybe the allocated area already big enough. (In particular, we always
878 * fall out here if the requested size is a decrease.)
879 *
880 * This memory context does not use power-of-2 chunk sizing and instead
881 * carves the chunks to be as small as possible, so most repalloc() calls
882 * will end up in the palloc/memcpy/pfree branch.
883 *
884 * XXX Perhaps we should annotate this condition with unlikely()?
885 */
886#ifdef MEMORY_CONTEXT_CHECKING
887 /* With MEMORY_CONTEXT_CHECKING, we need an extra byte for the sentinel */
888 if (oldsize > size)
889#else
890 if (oldsize >= size)
891#endif
892 {
893#ifdef MEMORY_CONTEXT_CHECKING
894 Size oldrequest = chunk->requested_size;
895
896#ifdef RANDOMIZE_ALLOCATED_MEMORY
897 /* We can only fill the extra space if we know the prior request */
898 if (size > oldrequest)
899 randomize_mem((char *) pointer + oldrequest,
900 size - oldrequest);
901#endif
902
903 chunk->requested_size = size;
904
905 /*
906 * If this is an increase, mark any newly-available part UNDEFINED.
907 * Otherwise, mark the obsolete part NOACCESS.
908 */
909 if (size > oldrequest)
910 VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest,
911 size - oldrequest);
912 else
913 VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size,
914 oldsize - size);
915
916 /* set mark to catch clobber of "unused" space */
917 set_sentinel(pointer, size);
918#else /* !MEMORY_CONTEXT_CHECKING */
919
920 /*
921 * We don't have the information to determine whether we're growing
922 * the old request or shrinking it, so we conservatively mark the
923 * entire new allocation DEFINED.
924 */
926 VALGRIND_MAKE_MEM_DEFINED(pointer, size);
927#endif
928
929 /* Disallow access to the chunk header. */
931
932 return pointer;
933 }
934
935 /* allocate new chunk (this also checks size is valid) */
936 newPointer = GenerationAlloc((MemoryContext) set, size, flags);
937
938 /* leave immediately if request was not completed */
939 if (newPointer == NULL)
940 {
941 /* Disallow access to the chunk header. */
943 return MemoryContextAllocationFailure((MemoryContext) set, size, flags);
944 }
945
946 /*
947 * GenerationAlloc() may have returned a region that is still NOACCESS.
948 * Change it to UNDEFINED for the moment; memcpy() will then transfer
949 * definedness from the old allocation to the new. If we know the old
950 * allocation, copy just that much. Otherwise, make the entire old chunk
951 * defined to avoid errors as we copy the currently-NOACCESS trailing
952 * bytes.
953 */
955#ifdef MEMORY_CONTEXT_CHECKING
956 oldsize = chunk->requested_size;
957#else
959#endif
960
961 /* transfer existing data (certain to fit) */
962 memcpy(newPointer, pointer, oldsize);
963
964 /* free old chunk */
965 GenerationFree(pointer);
966
967 return newPointer;
968}
969
970/*
971 * GenerationGetChunkContext
972 * Return the MemoryContext that 'pointer' belongs to.
973 */
975GenerationGetChunkContext(void *pointer)
976{
978 GenerationBlock *block;
979
980 /* Allow access to the chunk header. */
982
985 else
987
988 /* Disallow access to the chunk header. */
990
992 return &block->context->header;
993}
994
995/*
996 * GenerationGetChunkSpace
997 * Given a currently-allocated chunk, determine the total space
998 * it occupies (including all memory-allocation overhead).
999 */
1000Size
1001GenerationGetChunkSpace(void *pointer)
1002{
1005
1006 /* Allow access to the chunk header. */
1008
1010 {
1012
1014 chunksize = block->endptr - (char *) pointer;
1015 }
1016 else
1018
1019 /* Disallow access to the chunk header. */
1021
1023}
1024
1025/*
1026 * GenerationIsEmpty
1027 * Is a GenerationContext empty of any allocated space?
1028 */
1029bool
1031{
1032 GenerationContext *set = (GenerationContext *) context;
1033 dlist_iter iter;
1034
1036
1037 dlist_foreach(iter, &set->blocks)
1038 {
1039 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1040
1041 if (block->nchunks > 0)
1042 return false;
1043 }
1044
1045 return true;
1046}
1047
1048/*
1049 * GenerationStats
1050 * Compute stats about memory consumption of a Generation context.
1051 *
1052 * printfunc: if not NULL, pass a human-readable stats string to this.
1053 * passthru: pass this pointer through to printfunc.
1054 * totals: if not NULL, add stats about this context into *totals.
1055 * print_to_stderr: print stats to stderr if true, elog otherwise.
1056 *
1057 * XXX freespace only accounts for empty space at the end of the block, not
1058 * space of freed chunks (which is unknown).
1059 */
1060void
1064{
1065 GenerationContext *set = (GenerationContext *) context;
1066 Size nblocks = 0;
1067 Size nchunks = 0;
1068 Size nfreechunks = 0;
1069 Size totalspace;
1070 Size freespace = 0;
1071 dlist_iter iter;
1072
1074
1075 /* Include context header in totalspace */
1076 totalspace = MAXALIGN(sizeof(GenerationContext));
1077
1078 dlist_foreach(iter, &set->blocks)
1079 {
1080 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1081
1082 nblocks++;
1083 nchunks += block->nchunks;
1084 nfreechunks += block->nfree;
1085 totalspace += block->blksize;
1086 freespace += (block->endptr - block->freeptr);
1087 }
1088
1089 if (printfunc)
1090 {
1091 char stats_string[200];
1092
1094 "%zu total in %zu blocks (%zu chunks); %zu free (%zu chunks); %zu used",
1095 totalspace, nblocks, nchunks, freespace,
1096 nfreechunks, totalspace - freespace);
1098 }
1099
1100 if (totals)
1101 {
1102 totals->nblocks += nblocks;
1103 totals->freechunks += nfreechunks;
1104 totals->totalspace += totalspace;
1105 totals->freespace += freespace;
1106 }
1107}
1108
1109
1110#ifdef MEMORY_CONTEXT_CHECKING
1111
1112/*
1113 * GenerationCheck
1114 * Walk through chunks and check consistency of memory.
1115 *
1116 * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll
1117 * find yourself in an infinite loop when trouble occurs, because this
1118 * routine will be entered again when elog cleanup tries to release memory!
1119 */
1120void
1122{
1123 GenerationContext *gen = (GenerationContext *) context;
1124 const char *name = context->name;
1125 dlist_iter iter;
1127
1128 /* walk all blocks in this context */
1129 dlist_foreach(iter, &gen->blocks)
1130 {
1131 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1132 int nfree,
1133 nchunks;
1134 char *ptr;
1135 bool has_external_chunk = false;
1136
1137 total_allocated += block->blksize;
1138
1139 /*
1140 * nfree > nchunks is surely wrong. Equality is allowed as the block
1141 * might completely empty if it's the freeblock.
1142 */
1143 if (block->nfree > block->nchunks)
1144 elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
1145 name, block->nfree, block, block->nchunks);
1146
1147 /* check block belongs to the correct context */
1148 if (block->context != gen)
1149 elog(WARNING, "problem in Generation %s: bogus context link in block %p",
1150 name, block);
1151
1152 /* Now walk through the chunks and count them. */
1153 nfree = 0;
1154 nchunks = 0;
1155 ptr = ((char *) block) + Generation_BLOCKHDRSZ;
1156
1157 while (ptr < block->freeptr)
1158 {
1159 MemoryChunk *chunk = (MemoryChunk *) ptr;
1162
1163 /* Allow access to the chunk header. */
1165
1167 {
1169 chunksize = block->endptr - (char *) MemoryChunkGetPointer(chunk);
1170 has_external_chunk = true;
1171 }
1172 else
1173 {
1176 }
1177
1178 /* move to the next chunk */
1180
1181 nchunks += 1;
1182
1183 /* chunks have both block and context pointers, so check both */
1184 if (chunkblock != block)
1185 elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p",
1186 name, block, chunk);
1187
1188
1189 /* is chunk allocated? */
1190 if (chunk->requested_size != InvalidAllocSize)
1191 {
1192 /* now make sure the chunk size is correct */
1195 elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
1196 name, block, chunk);
1197
1198 /* check sentinel */
1199 Assert(chunk->requested_size < chunksize);
1200 if (!sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size))
1201 elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p",
1202 name, block, chunk);
1203 }
1204 else
1205 nfree += 1;
1206
1207 /* if chunk is allocated, disallow access to the chunk header */
1208 if (chunk->requested_size != InvalidAllocSize)
1210 }
1211
1212 /*
1213 * Make sure we got the expected number of allocated and free chunks
1214 * (as tracked in the block header).
1215 */
1216 if (nchunks != block->nchunks)
1217 elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d",
1218 name, nchunks, block, block->nchunks);
1219
1220 if (nfree != block->nfree)
1221 elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
1222 name, nfree, block, block->nfree);
1223
1224 if (has_external_chunk && nchunks > 1)
1225 elog(WARNING, "problem in Generation %s: external chunk on non-dedicated block %p",
1226 name, block);
1227
1228 }
1229
1231}
1232
1233#endif /* MEMORY_CONTEXT_CHECKING */
#define pg_noinline
Definition c.h:295
#define Min(x, y)
Definition c.h:997
#define likely(x)
Definition c.h:411
#define MAXALIGN(LEN)
Definition c.h:826
#define Max(x, y)
Definition c.h:991
#define Assert(condition)
Definition c.h:873
#define unlikely(x)
Definition c.h:412
uint32_t uint32
Definition c.h:546
#define StaticAssertDecl(condition, errmessage)
Definition c.h:942
size_t Size
Definition c.h:619
int errdetail(const char *fmt,...)
Definition elog.c:1216
int errcode(int sqlerrcode)
Definition elog.c:863
int errmsg(const char *fmt,...)
Definition elog.c:1080
#define WARNING
Definition elog.h:36
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:226
#define ereport(elevel,...)
Definition elog.h:150
void * GenerationRealloc(void *pointer, Size size, int flags)
Definition generation.c:829
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:976
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
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)
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
const char * name
Definition memnodes.h:131
dlist_node * cur
Definition ilist.h:179
const char * name

◆ FIRST_BLOCKHDRSZ

#define FIRST_BLOCKHDRSZ
Value:

Definition at line 48 of file generation.c.

◆ Generation_BLOCKHDRSZ

#define Generation_BLOCKHDRSZ   MAXALIGN(sizeof(GenerationBlock))

Definition at line 46 of file generation.c.

◆ Generation_CHUNK_FRACTION

#define Generation_CHUNK_FRACTION   8

Definition at line 51 of file generation.c.

◆ Generation_CHUNKHDRSZ

#define Generation_CHUNKHDRSZ   sizeof(MemoryChunk)

Definition at line 47 of file generation.c.

◆ GenerationBlockIsEmpty

#define GenerationBlockIsEmpty (   b)    ((b)->nchunks == 0)

Definition at line 118 of file generation.c.

◆ GenerationBlockIsValid

#define GenerationBlockIsValid (   block)     ((block) && GenerationIsValid((block)->context))

Definition at line 111 of file generation.c.

◆ GenerationIsValid

#define GenerationIsValid (   set)     ((set) && IsA(set, GenerationContext))

Definition at line 104 of file generation.c.

◆ IsKeeperBlock

#define IsKeeperBlock (   set,
  block 
)    ((block) == (KeeperBlock(set)))

Definition at line 134 of file generation.c.

◆ KeeperBlock

#define KeeperBlock (   set)
Value:
((GenerationBlock *) (((char *) set) + \

Definition at line 129 of file generation.c.

Typedef Documentation

◆ GenerationBlock

◆ GenerationContext

◆ GenerationPointer

Definition at line 55 of file generation.c.

Function Documentation

◆ GenerationAlloc()

void * GenerationAlloc ( MemoryContext  context,
Size  size,
int  flags 
)

Definition at line 553 of file generation.c.

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}
GenerationBlock * block
Definition generation.c:71
uint32 allocChunkLimit
Definition generation.c:69

References GenerationContext::allocChunkLimit, Assert, GenerationContext::block, fb(), GenerationContext::freeblock, Generation_CHUNKHDRSZ, GenerationAllocChunkFromBlock(), GenerationAllocFromNewBlock(), GenerationAllocLarge(), GenerationBlockFreeBytes(), GenerationBlockIsEmpty, GenerationIsValid, MAXALIGN, and unlikely.

Referenced by GenerationRealloc().

◆ GenerationAllocChunkFromBlock()

static void * GenerationAllocChunkFromBlock ( MemoryContext  context,
GenerationBlock block,
Size  size,
Size  chunk_size 
)
inlinestatic

Definition at line 436 of file generation.c.

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
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 */
464#endif
465
466 /* Ensure any padding bytes are marked NOACCESS. */
468 chunk_size - size);
469
470 /* Disallow access to the chunk header. */
472
474}

References Assert, GenerationBlock::endptr, fb(), GenerationBlock::freeptr, Generation_CHUNKHDRSZ, MCTX_GENERATION_ID, MemoryChunkGetPointer, MemoryChunkSetHdrMask(), GenerationBlock::nchunks, VALGRIND_MAKE_MEM_NOACCESS, and VALGRIND_MAKE_MEM_UNDEFINED.

Referenced by GenerationAlloc(), and GenerationAllocFromNewBlock().

◆ GenerationAllocFromNewBlock()

static pg_noinline void * GenerationAllocFromNewBlock ( MemoryContext  context,
Size  size,
int  flags,
Size  chunk_size 
)
static

Definition at line 484 of file generation.c.

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}

References GenerationContext::block, GenerationContext::blocks, dlist_push_head(), fb(), Generation_BLOCKHDRSZ, Generation_CHUNKHDRSZ, GenerationAllocChunkFromBlock(), GenerationBlockInit(), malloc, GenerationContext::maxBlockSize, MemoryContextData::mem_allocated, MemoryContextAllocationFailure(), GenerationContext::nextBlockSize, GenerationBlock::node, pg_nextpower2_size_t, and VALGRIND_MEMPOOL_ALLOC.

Referenced by GenerationAlloc().

◆ GenerationAllocLarge()

static pg_noinline void * GenerationAllocLarge ( MemoryContext  context,
Size  size,
int  flags 
)
static

Definition at line 363 of file generation.c.

364{
365 GenerationContext *set = (GenerationContext *) context;
366 GenerationBlock *block;
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 */
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
429}

References Assert, GenerationBlock::blksize, GenerationContext::blocks, GenerationBlock::context, dlist_push_head(), GenerationBlock::endptr, fb(), GenerationBlock::freeptr, Generation_BLOCKHDRSZ, Generation_CHUNKHDRSZ, malloc, MAXALIGN, MCTX_GENERATION_ID, MemoryContextData::mem_allocated, MemoryChunkGetPointer, MemoryChunkSetHdrMaskExternal(), MemoryContextAllocationFailure(), MemoryContextCheckSize(), GenerationBlock::nchunks, GenerationBlock::nfree, GenerationBlock::node, VALGRIND_MAKE_MEM_NOACCESS, and VALGRIND_MEMPOOL_ALLOC.

Referenced by GenerationAlloc().

◆ GenerationBlockFree()

static void GenerationBlockFree ( GenerationContext set,
GenerationBlock block 
)
inlinestatic

Definition at line 690 of file generation.c.

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}

References Assert, GenerationBlock::blksize, dlist_delete(), fb(), free, GenerationContext::freeblock, IsKeeperBlock, GenerationBlock::node, and VALGRIND_MEMPOOL_FREE.

Referenced by GenerationFree(), and GenerationReset().

◆ GenerationBlockFreeBytes()

static Size GenerationBlockFreeBytes ( GenerationBlock block)
inlinestatic

Definition at line 680 of file generation.c.

681{
682 return (block->endptr - block->freeptr);
683}

References GenerationBlock::endptr, and GenerationBlock::freeptr.

Referenced by GenerationAlloc().

◆ GenerationBlockInit()

static void GenerationBlockInit ( GenerationContext context,
GenerationBlock block,
Size  blksize 
)
inlinestatic

Definition at line 635 of file generation.c.

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}

References GenerationBlock::blksize, GenerationBlock::context, GenerationBlock::endptr, GenerationBlock::freeptr, Generation_BLOCKHDRSZ, GenerationBlock::nchunks, GenerationBlock::nfree, and VALGRIND_MAKE_MEM_NOACCESS.

Referenced by GenerationAllocFromNewBlock(), and GenerationContextCreate().

◆ GenerationBlockMarkEmpty()

static void GenerationBlockMarkEmpty ( GenerationBlock block)
inlinestatic

Definition at line 656 of file generation.c.

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}

References fb(), GenerationBlock::freeptr, Generation_BLOCKHDRSZ, GenerationBlock::nchunks, GenerationBlock::nfree, and VALGRIND_MAKE_MEM_NOACCESS.

Referenced by GenerationFree(), and GenerationReset().

◆ GenerationContextCreate()

MemoryContext GenerationContextCreate ( MemoryContext  parent,
const char name,
Size  minContextSize,
Size  initBlockSize,
Size  maxBlockSize 
)

Definition at line 162 of file generation.c.

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}

References GenerationContext::allocChunkLimit, AllocHugeSizeIsValid, Assert, GenerationContext::block, GenerationContext::blocks, dlist_init(), dlist_push_head(), ereport, errcode(), errdetail(), errmsg(), ERROR, fb(), FIRST_BLOCKHDRSZ, GenerationContext::freeblock, Generation_BLOCKHDRSZ, Generation_CHUNK_FRACTION, Generation_CHUNKHDRSZ, GenerationBlockInit(), GenerationContext::initBlockSize, KeeperBlock, malloc, Max, MAXALIGN, GenerationContext::maxBlockSize, MCTX_GENERATION_ID, MEMORYCHUNK_MAX_BLOCKOFFSET, MEMORYCHUNK_MAX_VALUE, MemoryContextCreate(), MemoryContextStats(), Min, name, GenerationContext::nextBlockSize, GenerationBlock::node, StaticAssertDecl, TopMemoryContext, VALGRIND_CREATE_MEMPOOL, and VALGRIND_MEMPOOL_ALLOC.

Referenced by gistvacuumscan(), ReorderBufferAllocate(), and tuplestore_begin_common().

◆ GenerationDelete()

void GenerationDelete ( MemoryContext  context)

Definition at line 344 of file generation.c.

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}

References free, GenerationReset(), and VALGRIND_DESTROY_MEMPOOL.

◆ GenerationFree()

void GenerationFree ( void pointer)

Definition at line 718 of file generation.c.

719{
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
732 {
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 /* Test for someone scribbling on unused space in chunk */
766 Assert(chunk->requested_size < chunksize);
767 if (!sentinel_ok(pointer, chunk->requested_size))
768 elog(WARNING, "detected write past chunk end in %s %p",
769 ((MemoryContext) block->context)->name, chunk);
770#endif
771
772#ifdef CLOBBER_FREED_MEMORY
773 wipe_mem(pointer, chunksize);
774#endif
775
776#ifdef MEMORY_CONTEXT_CHECKING
777 /* Reset requested_size to InvalidAllocSize in freed chunks */
778 chunk->requested_size = InvalidAllocSize;
779#endif
780
781 block->nfree += 1;
782
783 Assert(block->nchunks > 0);
784 Assert(block->nfree <= block->nchunks);
785 Assert(block != block->context->freeblock);
786
787 /* If there are still allocated chunks in the block, we're done. */
788 if (likely(block->nfree < block->nchunks))
789 return;
790
791 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 */
810 if (IsKeeperBlock(set, block) || set->block == block)
811 GenerationBlockMarkEmpty(block); /* case 1 and 2 */
812 else if (set->freeblock == NULL)
813 {
814 /* case 3 */
816 set->freeblock = block;
817 }
818 else
819 GenerationBlockFree(set, block); /* Otherwise, free it */
820}

References Assert, GenerationContext::block, GenerationBlock::context, elog, GenerationBlock::endptr, ERROR, ExternalChunkGetBlock, fb(), GenerationContext::freeblock, Generation_CHUNKHDRSZ, GenerationBlockFree(), GenerationBlockIsValid, GenerationBlockMarkEmpty(), InvalidAllocSize, IsKeeperBlock, likely, MemoryChunkGetBlock(), MemoryChunkGetValue(), MemoryChunkIsExternal(), GenerationBlock::nchunks, GenerationBlock::nfree, PointerGetMemoryChunk, VALGRIND_MAKE_MEM_DEFINED, and WARNING.

Referenced by GenerationRealloc().

◆ GenerationGetChunkContext()

MemoryContext GenerationGetChunkContext ( void pointer)

◆ GenerationGetChunkSpace()

Size GenerationGetChunkSpace ( void pointer)

Definition at line 1002 of file generation.c.

1003{
1006
1007 /* Allow access to the chunk header. */
1009
1011 {
1013
1015 chunksize = block->endptr - (char *) pointer;
1016 }
1017 else
1019
1020 /* Disallow access to the chunk header. */
1022
1024}

References Assert, GenerationBlock::endptr, ExternalChunkGetBlock, fb(), Generation_CHUNKHDRSZ, GenerationBlockIsValid, MemoryChunkGetValue(), MemoryChunkIsExternal(), PointerGetMemoryChunk, VALGRIND_MAKE_MEM_DEFINED, and VALGRIND_MAKE_MEM_NOACCESS.

◆ GenerationIsEmpty()

bool GenerationIsEmpty ( MemoryContext  context)

Definition at line 1031 of file generation.c.

1032{
1033 GenerationContext *set = (GenerationContext *) context;
1034 dlist_iter iter;
1035
1037
1038 dlist_foreach(iter, &set->blocks)
1039 {
1040 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1041
1042 if (block->nchunks > 0)
1043 return false;
1044 }
1045
1046 return true;
1047}

References Assert, GenerationContext::blocks, dlist_iter::cur, dlist_container, dlist_foreach, GenerationIsValid, and GenerationBlock::nchunks.

◆ GenerationRealloc()

void * GenerationRealloc ( void pointer,
Size  size,
int  flags 
)

Definition at line 829 of file generation.c.

830{
833 GenerationBlock *block;
836
837 /* Allow access to the chunk header. */
839
841 {
843
844 /*
845 * Try to verify that we have a sane block pointer: the block header
846 * should reference a generation context.
847 */
848 if (!GenerationBlockIsValid(block))
849 elog(ERROR, "could not find block containing chunk %p", chunk);
850
851 oldsize = block->endptr - (char *) pointer;
852 }
853 else
854 {
855 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 */
863
865 }
866
867 set = block->context;
868
869#ifdef MEMORY_CONTEXT_CHECKING
870 /* Test for someone scribbling on unused space in chunk */
871 Assert(chunk->requested_size < oldsize);
872 if (!sentinel_ok(pointer, chunk->requested_size))
873 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 */
889 if (oldsize > size)
890#else
891 if (oldsize >= size)
892#endif
893 {
894#ifdef MEMORY_CONTEXT_CHECKING
895 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 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 */
918 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 */
927 VALGRIND_MAKE_MEM_DEFINED(pointer, size);
928#endif
929
930 /* Disallow access to the chunk header. */
932
933 return pointer;
934 }
935
936 /* allocate new chunk (this also checks size is valid) */
937 newPointer = GenerationAlloc((MemoryContext) set, size, flags);
938
939 /* leave immediately if request was not completed */
940 if (newPointer == NULL)
941 {
942 /* Disallow access to the chunk header. */
944 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 */
956#ifdef MEMORY_CONTEXT_CHECKING
957 oldsize = chunk->requested_size;
958#else
960#endif
961
962 /* transfer existing data (certain to fit) */
963 memcpy(newPointer, pointer, oldsize);
964
965 /* free old chunk */
966 GenerationFree(pointer);
967
968 return newPointer;
969}

References Assert, GenerationBlock::context, elog, GenerationBlock::endptr, ERROR, ExternalChunkGetBlock, fb(), Generation_CHUNKHDRSZ, GenerationAlloc(), GenerationBlockIsValid, GenerationFree(), MemoryChunkGetBlock(), MemoryChunkGetValue(), MemoryChunkIsExternal(), MemoryContextAllocationFailure(), name, PointerGetMemoryChunk, VALGRIND_MAKE_MEM_DEFINED, VALGRIND_MAKE_MEM_NOACCESS, VALGRIND_MAKE_MEM_UNDEFINED, and WARNING.

◆ GenerationReset()

void GenerationReset ( MemoryContext  context)

Definition at line 291 of file generation.c.

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}

References Assert, GenerationContext::block, GenerationContext::blocks, dlist_container, dlist_foreach_modify, dlist_has_next(), dlist_head_node(), dlist_is_empty(), fb(), FIRST_BLOCKHDRSZ, GenerationContext::freeblock, GenerationBlockFree(), GenerationBlockMarkEmpty(), GenerationIsValid, GenerationContext::initBlockSize, IsKeeperBlock, KeeperBlock, GenerationContext::nextBlockSize, and VALGRIND_MEMPOOL_TRIM.

Referenced by GenerationDelete().

◆ GenerationStats()

void GenerationStats ( MemoryContext  context,
MemoryStatsPrintFunc  printfunc,
void passthru,
MemoryContextCounters totals,
bool  print_to_stderr 
)

Definition at line 1062 of file generation.c.

1065{
1066 GenerationContext *set = (GenerationContext *) context;
1067 Size nblocks = 0;
1068 Size nchunks = 0;
1069 Size nfreechunks = 0;
1070 Size totalspace;
1071 Size freespace = 0;
1072 dlist_iter iter;
1073
1075
1076 /* Include context header in totalspace */
1077 totalspace = MAXALIGN(sizeof(GenerationContext));
1078
1079 dlist_foreach(iter, &set->blocks)
1080 {
1081 GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1082
1083 nblocks++;
1084 nchunks += block->nchunks;
1085 nfreechunks += block->nfree;
1086 totalspace += block->blksize;
1087 freespace += (block->endptr - block->freeptr);
1088 }
1089
1090 if (printfunc)
1091 {
1092 char stats_string[200];
1093
1095 "%zu total in %zu blocks (%zu chunks); %zu free (%zu chunks); %zu used",
1096 totalspace, nblocks, nchunks, freespace,
1097 nfreechunks, totalspace - freespace);
1099 }
1100
1101 if (totals)
1102 {
1103 totals->nblocks += nblocks;
1104 totals->freechunks += nfreechunks;
1105 totals->totalspace += totalspace;
1106 totals->freespace += freespace;
1107 }
1108}

References Assert, GenerationBlock::blksize, GenerationContext::blocks, dlist_iter::cur, dlist_container, dlist_foreach, GenerationBlock::endptr, fb(), GenerationBlock::freeptr, GenerationIsValid, MAXALIGN, GenerationBlock::nchunks, GenerationBlock::nfree, and snprintf.