PostgreSQL Source Code  git master
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-2023, 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 
49 #define Generation_CHUNK_FRACTION 8
50 
51 typedef struct GenerationBlock GenerationBlock; /* forward reference */
52 
53 typedef void *GenerationPointer;
54 
55 /*
56  * GenerationContext is a simple memory context not reusing allocated chunks,
57  * and freeing blocks once all chunks are freed.
58  */
59 typedef struct GenerationContext
60 {
61  MemoryContextData header; /* Standard memory-context fields */
62 
63  /* Generational context parameters */
64  uint32 initBlockSize; /* initial block size */
65  uint32 maxBlockSize; /* maximum block size */
66  uint32 nextBlockSize; /* next block size to allocate */
67  uint32 allocChunkLimit; /* effective chunk size limit */
68 
69  GenerationBlock *block; /* current (most recently allocated) block, or
70  * NULL if we've just freed the most recent
71  * block */
72  GenerationBlock *freeblock; /* pointer to a block that's being recycled,
73  * 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  (PointerIsValid(set) && IsA(set, GenerationContext))
106 
107 /*
108  * GenerationBlockIsValid
109  * True iff block is valid block of generation set.
110  */
111 #define GenerationBlockIsValid(block) \
112  (PointerIsValid(block) && GenerationIsValid((block)->context))
113 
114 /*
115  * We always store external chunks on a dedicated block. This makes fetching
116  * the block from an external chunk easy since it's always the first and only
117  * chunk on the block.
118  */
119 #define ExternalChunkGetBlock(chunk) \
120  (GenerationBlock *) ((char *) chunk - Generation_BLOCKHDRSZ)
121 
122 /* Obtain the keeper block for a generation context */
123 #define KeeperBlock(set) \
124  ((GenerationBlock *) (((char *) set) + \
125  MAXALIGN(sizeof(GenerationContext))))
126 
127 /* Check if the block is the keeper block of the given generation context */
128 #define IsKeeperBlock(set, block) ((block) == (KeeperBlock(set)))
129 
130 /* Inlined helper functions */
131 static inline void GenerationBlockInit(GenerationContext *context,
132  GenerationBlock *block,
133  Size blksize);
134 static inline bool GenerationBlockIsEmpty(GenerationBlock *block);
135 static inline void GenerationBlockMarkEmpty(GenerationBlock *block);
136 static inline Size GenerationBlockFreeBytes(GenerationBlock *block);
137 static inline void GenerationBlockFree(GenerationContext *set,
138  GenerationBlock *block);
139 
140 
141 /*
142  * Public routines
143  */
144 
145 
146 /*
147  * GenerationContextCreate
148  * Create a new Generation context.
149  *
150  * parent: parent context, or NULL if top-level context
151  * name: name of context (must be statically allocated)
152  * minContextSize: minimum context size
153  * initBlockSize: initial allocation block size
154  * maxBlockSize: maximum allocation block size
155  */
158  const char *name,
159  Size minContextSize,
160  Size initBlockSize,
161  Size maxBlockSize)
162 {
163  Size firstBlockSize;
164  Size allocSize;
165  GenerationContext *set;
166  GenerationBlock *block;
167 
168  /* ensure MemoryChunk's size is properly maxaligned */
170  "sizeof(MemoryChunk) is not maxaligned");
171 
172  /*
173  * First, validate allocation parameters. Asserts seem sufficient because
174  * nobody varies their parameters at runtime. We somewhat arbitrarily
175  * enforce a minimum 1K block size. We restrict the maximum block size to
176  * MEMORYCHUNK_MAX_BLOCKOFFSET as MemoryChunks are limited to this in
177  * regards to addressing the offset between the chunk and the block that
178  * the chunk is stored on. We would be unable to store the offset between
179  * the chunk and block for any chunks that were beyond
180  * MEMORYCHUNK_MAX_BLOCKOFFSET bytes into the block if the block was to be
181  * larger than this.
182  */
183  Assert(initBlockSize == MAXALIGN(initBlockSize) &&
184  initBlockSize >= 1024);
185  Assert(maxBlockSize == MAXALIGN(maxBlockSize) &&
186  maxBlockSize >= initBlockSize &&
187  AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
188  Assert(minContextSize == 0 ||
189  (minContextSize == MAXALIGN(minContextSize) &&
190  minContextSize >= 1024 &&
191  minContextSize <= maxBlockSize));
192  Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
193 
194  /* Determine size of initial block */
195  allocSize = MAXALIGN(sizeof(GenerationContext)) +
197  if (minContextSize != 0)
198  allocSize = Max(allocSize, minContextSize);
199  else
200  allocSize = Max(allocSize, initBlockSize);
201 
202  /*
203  * Allocate the initial block. Unlike other generation.c blocks, it
204  * starts with the context header and its block header follows that.
205  */
206  set = (GenerationContext *) malloc(allocSize);
207  if (set == NULL)
208  {
210  ereport(ERROR,
211  (errcode(ERRCODE_OUT_OF_MEMORY),
212  errmsg("out of memory"),
213  errdetail("Failed while creating memory context \"%s\".",
214  name)));
215  }
216 
217  /*
218  * Avoid writing code that can fail between here and MemoryContextCreate;
219  * we'd leak the header if we ereport in this stretch.
220  */
221  dlist_init(&set->blocks);
222 
223  /* Fill in the initial block's block header */
224  block = KeeperBlock(set);
225  /* determine the block size and initialize it */
226  firstBlockSize = allocSize - MAXALIGN(sizeof(GenerationContext));
227  GenerationBlockInit(set, block, firstBlockSize);
228 
229  /* add it to the doubly-linked list of blocks */
230  dlist_push_head(&set->blocks, &block->node);
231 
232  /* use it as the current allocation block */
233  set->block = block;
234 
235  /* No free block, yet */
236  set->freeblock = NULL;
237 
238  /* Fill in GenerationContext-specific header fields */
239  set->initBlockSize = (uint32) initBlockSize;
240  set->maxBlockSize = (uint32) maxBlockSize;
241  set->nextBlockSize = (uint32) initBlockSize;
242 
243  /*
244  * Compute the allocation chunk size limit for this context.
245  *
246  * Limit the maximum size a non-dedicated chunk can be so that we can fit
247  * at least Generation_CHUNK_FRACTION of chunks this big onto the maximum
248  * sized block. We must further limit this value so that it's no more
249  * than MEMORYCHUNK_MAX_VALUE. We're unable to have non-external chunks
250  * larger than that value as we store the chunk size in the MemoryChunk
251  * 'value' field in the call to MemoryChunkSetHdrMask().
252  */
253  set->allocChunkLimit = Min(maxBlockSize, MEMORYCHUNK_MAX_VALUE);
254  while ((Size) (set->allocChunkLimit + Generation_CHUNKHDRSZ) >
255  (Size) ((Size) (maxBlockSize - Generation_BLOCKHDRSZ) / Generation_CHUNK_FRACTION))
256  set->allocChunkLimit >>= 1;
257 
258  /* Finally, do the type-independent part of context creation */
260  T_GenerationContext,
262  parent,
263  name);
264 
265  ((MemoryContext) set)->mem_allocated = firstBlockSize;
266 
267  return (MemoryContext) set;
268 }
269 
270 /*
271  * GenerationReset
272  * Frees all memory which is allocated in the given set.
273  *
274  * The code simply frees all the blocks in the context - we don't keep any
275  * keeper blocks or anything like that.
276  */
277 void
279 {
280  GenerationContext *set = (GenerationContext *) context;
281  dlist_mutable_iter miter;
282 
284 
285 #ifdef MEMORY_CONTEXT_CHECKING
286  /* Check for corruption and leaks before freeing */
287  GenerationCheck(context);
288 #endif
289 
290  /*
291  * NULLify the free block pointer. We must do this before calling
292  * GenerationBlockFree as that function never expects to free the
293  * freeblock.
294  */
295  set->freeblock = NULL;
296 
297  dlist_foreach_modify(miter, &set->blocks)
298  {
299  GenerationBlock *block = dlist_container(GenerationBlock, node, miter.cur);
300 
301  if (IsKeeperBlock(set, block))
303  else
304  GenerationBlockFree(set, block);
305  }
306 
307  /* set it so new allocations to make use of the keeper block */
308  set->block = KeeperBlock(set);
309 
310  /* Reset block size allocation sequence, too */
311  set->nextBlockSize = set->initBlockSize;
312 
313  /* Ensure there is only 1 item in the dlist */
314  Assert(!dlist_is_empty(&set->blocks));
316 }
317 
318 /*
319  * GenerationDelete
320  * Free all memory which is allocated in the given context.
321  */
322 void
324 {
325  /* Reset to release all releasable GenerationBlocks */
326  GenerationReset(context);
327  /* And free the context header and keeper block */
328  free(context);
329 }
330 
331 /*
332  * GenerationAlloc
333  * Returns pointer to allocated memory of given size or NULL if
334  * request could not be completed; memory is added to the set.
335  *
336  * No request may exceed:
337  * MAXALIGN_DOWN(SIZE_MAX) - Generation_BLOCKHDRSZ - Generation_CHUNKHDRSZ
338  * All callers use a much-lower limit.
339  *
340  * Note: when using valgrind, it doesn't matter how the returned allocation
341  * is marked, as mcxt.c will set it to UNDEFINED. In some paths we will
342  * return space that is marked NOACCESS - GenerationRealloc has to beware!
343  */
344 void *
346 {
347  GenerationContext *set = (GenerationContext *) context;
348  GenerationBlock *block;
349  MemoryChunk *chunk;
350  Size chunk_size;
351  Size required_size;
352 
354 
355 #ifdef MEMORY_CONTEXT_CHECKING
356  /* ensure there's always space for the sentinel byte */
357  chunk_size = MAXALIGN(size + 1);
358 #else
359  chunk_size = MAXALIGN(size);
360 #endif
361  required_size = chunk_size + Generation_CHUNKHDRSZ;
362 
363  /* is it an over-sized chunk? if yes, allocate special block */
364  if (chunk_size > set->allocChunkLimit)
365  {
366  Size blksize = required_size + Generation_BLOCKHDRSZ;
367 
368  block = (GenerationBlock *) malloc(blksize);
369  if (block == NULL)
370  return NULL;
371 
372  context->mem_allocated += blksize;
373 
374  /* block with a single (used) chunk */
375  block->context = set;
376  block->blksize = blksize;
377  block->nchunks = 1;
378  block->nfree = 0;
379 
380  /* the block is completely full */
381  block->freeptr = block->endptr = ((char *) block) + blksize;
382 
383  chunk = (MemoryChunk *) (((char *) block) + Generation_BLOCKHDRSZ);
384 
385  /* mark the MemoryChunk as externally managed */
387 
388 #ifdef MEMORY_CONTEXT_CHECKING
389  chunk->requested_size = size;
390  /* set mark to catch clobber of "unused" space */
391  Assert(size < chunk_size);
392  set_sentinel(MemoryChunkGetPointer(chunk), size);
393 #endif
394 #ifdef RANDOMIZE_ALLOCATED_MEMORY
395  /* fill the allocated space with junk */
396  randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
397 #endif
398 
399  /* add the block to the list of allocated blocks */
400  dlist_push_head(&set->blocks, &block->node);
401 
402  /* Ensure any padding bytes are marked NOACCESS. */
403  VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
404  chunk_size - size);
405 
406  /* Disallow access to the chunk header. */
408 
409  return MemoryChunkGetPointer(chunk);
410  }
411 
412  /*
413  * Not an oversized chunk. We try to first make use of the current block,
414  * but if there's not enough space in it, instead of allocating a new
415  * block, we look to see if the freeblock is empty and has enough space.
416  * If not, we'll also try the same using the keeper block. The keeper
417  * block may have become empty and we have no other way to reuse it again
418  * if we don't try to use it explicitly here.
419  *
420  * We don't want to start filling the freeblock before the current block
421  * is full, otherwise we may cause fragmentation in FIFO type workloads.
422  * We only switch to using the freeblock or keeper block if those blocks
423  * are completely empty. If we didn't do that we could end up fragmenting
424  * consecutive allocations over multiple blocks which would be a problem
425  * that would compound over time.
426  */
427  block = set->block;
428 
429  if (block == NULL ||
430  GenerationBlockFreeBytes(block) < required_size)
431  {
432  Size blksize;
433  GenerationBlock *freeblock = set->freeblock;
434 
435  if (freeblock != NULL &&
436  GenerationBlockIsEmpty(freeblock) &&
437  GenerationBlockFreeBytes(freeblock) >= required_size)
438  {
439  block = freeblock;
440 
441  /*
442  * Zero out the freeblock as we'll set this to the current block
443  * below
444  */
445  set->freeblock = NULL;
446  }
447  else if (GenerationBlockIsEmpty(KeeperBlock(set)) &&
448  GenerationBlockFreeBytes(KeeperBlock(set)) >= required_size)
449  {
450  block = KeeperBlock(set);
451  }
452  else
453  {
454  /*
455  * The first such block has size initBlockSize, and we double the
456  * space in each succeeding block, but not more than maxBlockSize.
457  */
458  blksize = set->nextBlockSize;
459  set->nextBlockSize <<= 1;
460  if (set->nextBlockSize > set->maxBlockSize)
461  set->nextBlockSize = set->maxBlockSize;
462 
463  /* we'll need a block hdr too, so add that to the required size */
464  required_size += Generation_BLOCKHDRSZ;
465 
466  /* round the size up to the next power of 2 */
467  if (blksize < required_size)
468  blksize = pg_nextpower2_size_t(required_size);
469 
470  block = (GenerationBlock *) malloc(blksize);
471 
472  if (block == NULL)
473  return NULL;
474 
475  context->mem_allocated += blksize;
476 
477  /* initialize the new block */
478  GenerationBlockInit(set, block, blksize);
479 
480  /* add it to the doubly-linked list of blocks */
481  dlist_push_head(&set->blocks, &block->node);
482 
483  /* Zero out the freeblock in case it's become full */
484  set->freeblock = NULL;
485  }
486 
487  /* and also use it as the current allocation block */
488  set->block = block;
489  }
490 
491  /* we're supposed to have a block with enough free space now */
492  Assert(block != NULL);
493  Assert((block->endptr - block->freeptr) >= Generation_CHUNKHDRSZ + chunk_size);
494 
495  chunk = (MemoryChunk *) block->freeptr;
496 
497  /* Prepare to initialize the chunk header. */
499 
500  block->nchunks += 1;
501  block->freeptr += (Generation_CHUNKHDRSZ + chunk_size);
502 
503  Assert(block->freeptr <= block->endptr);
504 
505  MemoryChunkSetHdrMask(chunk, block, chunk_size, MCTX_GENERATION_ID);
506 #ifdef MEMORY_CONTEXT_CHECKING
507  chunk->requested_size = size;
508  /* set mark to catch clobber of "unused" space */
509  Assert(size < chunk_size);
510  set_sentinel(MemoryChunkGetPointer(chunk), size);
511 #endif
512 #ifdef RANDOMIZE_ALLOCATED_MEMORY
513  /* fill the allocated space with junk */
514  randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
515 #endif
516 
517  /* Ensure any padding bytes are marked NOACCESS. */
518  VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
519  chunk_size - size);
520 
521  /* Disallow access to the chunk header. */
523 
524  return MemoryChunkGetPointer(chunk);
525 }
526 
527 /*
528  * GenerationBlockInit
529  * Initializes 'block' assuming 'blksize'. Does not update the context's
530  * mem_allocated field.
531  */
532 static inline void
534  Size blksize)
535 {
536  block->context = context;
537  block->blksize = blksize;
538  block->nchunks = 0;
539  block->nfree = 0;
540 
541  block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
542  block->endptr = ((char *) block) + blksize;
543 
544  /* Mark unallocated space NOACCESS. */
546  blksize - Generation_BLOCKHDRSZ);
547 }
548 
549 /*
550  * GenerationBlockIsEmpty
551  * Returns true iff 'block' contains no chunks
552  */
553 static inline bool
555 {
556  return (block->nchunks == 0);
557 }
558 
559 /*
560  * GenerationBlockMarkEmpty
561  * Set a block as empty. Does not free the block.
562  */
563 static inline void
565 {
566 #if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
567  char *datastart = ((char *) block) + Generation_BLOCKHDRSZ;
568 #endif
569 
570 #ifdef CLOBBER_FREED_MEMORY
571  wipe_mem(datastart, block->freeptr - datastart);
572 #else
573  /* wipe_mem() would have done this */
574  VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
575 #endif
576 
577  /* Reset the block, but don't return it to malloc */
578  block->nchunks = 0;
579  block->nfree = 0;
580  block->freeptr = ((char *) block) + Generation_BLOCKHDRSZ;
581 }
582 
583 /*
584  * GenerationBlockFreeBytes
585  * Returns the number of bytes free in 'block'
586  */
587 static inline Size
589 {
590  return (block->endptr - block->freeptr);
591 }
592 
593 /*
594  * GenerationBlockFree
595  * Remove 'block' from 'set' and release the memory consumed by it.
596  */
597 static inline void
599 {
600  /* Make sure nobody tries to free the keeper block */
601  Assert(!IsKeeperBlock(set, block));
602  /* We shouldn't be freeing the freeblock either */
603  Assert(block != set->freeblock);
604 
605  /* release the block from the list of blocks */
606  dlist_delete(&block->node);
607 
608  ((MemoryContext) set)->mem_allocated -= block->blksize;
609 
610 #ifdef CLOBBER_FREED_MEMORY
611  wipe_mem(block, block->blksize);
612 #endif
613 
614  free(block);
615 }
616 
617 /*
618  * GenerationFree
619  * Update number of chunks in the block, and if all chunks in the block
620  * are now free then discard the block.
621  */
622 void
623 GenerationFree(void *pointer)
624 {
625  MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
626  GenerationBlock *block;
627  GenerationContext *set;
628 #if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
629  || defined(CLOBBER_FREED_MEMORY)
630  Size chunksize;
631 #endif
632 
633  /* Allow access to the chunk header. */
635 
636  if (MemoryChunkIsExternal(chunk))
637  {
638  block = ExternalChunkGetBlock(chunk);
639 
640  /*
641  * Try to verify that we have a sane block pointer: the block header
642  * should reference a generation context.
643  */
644  if (!GenerationBlockIsValid(block))
645  elog(ERROR, "could not find block containing chunk %p", chunk);
646 
647 #if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
648  || defined(CLOBBER_FREED_MEMORY)
649  chunksize = block->endptr - (char *) pointer;
650 #endif
651  }
652  else
653  {
654  block = MemoryChunkGetBlock(chunk);
655 
656  /*
657  * In this path, for speed reasons we just Assert that the referenced
658  * block is good. Future field experience may show that this Assert
659  * had better become a regular runtime test-and-elog check.
660  */
662 
663 #if (defined(MEMORY_CONTEXT_CHECKING) && defined(USE_ASSERT_CHECKING)) \
664  || defined(CLOBBER_FREED_MEMORY)
665  chunksize = MemoryChunkGetValue(chunk);
666 #endif
667  }
668 
669 #ifdef MEMORY_CONTEXT_CHECKING
670  /* Test for someone scribbling on unused space in chunk */
671  Assert(chunk->requested_size < chunksize);
672  if (!sentinel_ok(pointer, chunk->requested_size))
673  elog(WARNING, "detected write past chunk end in %s %p",
674  ((MemoryContext) block->context)->name, chunk);
675 #endif
676 
677 #ifdef CLOBBER_FREED_MEMORY
678  wipe_mem(pointer, chunksize);
679 #endif
680 
681 #ifdef MEMORY_CONTEXT_CHECKING
682  /* Reset requested_size to InvalidAllocSize in freed chunks */
683  chunk->requested_size = InvalidAllocSize;
684 #endif
685 
686  block->nfree += 1;
687 
688  Assert(block->nchunks > 0);
689  Assert(block->nfree <= block->nchunks);
690 
691  /* If there are still allocated chunks in the block, we're done. */
692  if (block->nfree < block->nchunks)
693  return;
694 
695  set = block->context;
696 
697  /* Don't try to free the keeper block, just mark it empty */
698  if (IsKeeperBlock(set, block))
699  {
701  return;
702  }
703 
704  /*
705  * If there is no freeblock set or if this is the freeblock then instead
706  * of freeing this memory, we keep it around so that new allocations have
707  * the option of recycling it.
708  */
709  if (set->freeblock == NULL || set->freeblock == block)
710  {
711  /* XXX should we only recycle maxBlockSize sized blocks? */
712  set->freeblock = block;
714  return;
715  }
716 
717  /* Also make sure the block is not marked as the current block. */
718  if (set->block == block)
719  set->block = NULL;
720 
721  /*
722  * The block is empty, so let's get rid of it. First remove it from the
723  * list of blocks, then return it to malloc().
724  */
725  dlist_delete(&block->node);
726 
727  set->header.mem_allocated -= block->blksize;
728  free(block);
729 }
730 
731 /*
732  * GenerationRealloc
733  * When handling repalloc, we simply allocate a new chunk, copy the data
734  * and discard the old one. The only exception is when the new size fits
735  * into the old chunk - in that case we just update chunk header.
736  */
737 void *
738 GenerationRealloc(void *pointer, Size size)
739 {
740  MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
741  GenerationContext *set;
742  GenerationBlock *block;
743  GenerationPointer newPointer;
744  Size oldsize;
745 
746  /* Allow access to the chunk header. */
748 
749  if (MemoryChunkIsExternal(chunk))
750  {
751  block = ExternalChunkGetBlock(chunk);
752 
753  /*
754  * Try to verify that we have a sane block pointer: the block header
755  * should reference a generation context.
756  */
757  if (!GenerationBlockIsValid(block))
758  elog(ERROR, "could not find block containing chunk %p", chunk);
759 
760  oldsize = block->endptr - (char *) pointer;
761  }
762  else
763  {
764  block = MemoryChunkGetBlock(chunk);
765 
766  /*
767  * In this path, for speed reasons we just Assert that the referenced
768  * block is good. Future field experience may show that this Assert
769  * had better become a regular runtime test-and-elog check.
770  */
772 
773  oldsize = MemoryChunkGetValue(chunk);
774  }
775 
776  set = block->context;
777 
778 #ifdef MEMORY_CONTEXT_CHECKING
779  /* Test for someone scribbling on unused space in chunk */
780  Assert(chunk->requested_size < oldsize);
781  if (!sentinel_ok(pointer, chunk->requested_size))
782  elog(WARNING, "detected write past chunk end in %s %p",
783  ((MemoryContext) set)->name, chunk);
784 #endif
785 
786  /*
787  * Maybe the allocated area already is >= the new size. (In particular,
788  * we always fall out here if the requested size is a decrease.)
789  *
790  * This memory context does not use power-of-2 chunk sizing and instead
791  * carves the chunks to be as small as possible, so most repalloc() calls
792  * will end up in the palloc/memcpy/pfree branch.
793  *
794  * XXX Perhaps we should annotate this condition with unlikely()?
795  */
796  if (oldsize >= size)
797  {
798 #ifdef MEMORY_CONTEXT_CHECKING
799  Size oldrequest = chunk->requested_size;
800 
801 #ifdef RANDOMIZE_ALLOCATED_MEMORY
802  /* We can only fill the extra space if we know the prior request */
803  if (size > oldrequest)
804  randomize_mem((char *) pointer + oldrequest,
805  size - oldrequest);
806 #endif
807 
808  chunk->requested_size = size;
809 
810  /*
811  * If this is an increase, mark any newly-available part UNDEFINED.
812  * Otherwise, mark the obsolete part NOACCESS.
813  */
814  if (size > oldrequest)
815  VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest,
816  size - oldrequest);
817  else
818  VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size,
819  oldsize - size);
820 
821  /* set mark to catch clobber of "unused" space */
822  set_sentinel(pointer, size);
823 #else /* !MEMORY_CONTEXT_CHECKING */
824 
825  /*
826  * We don't have the information to determine whether we're growing
827  * the old request or shrinking it, so we conservatively mark the
828  * entire new allocation DEFINED.
829  */
830  VALGRIND_MAKE_MEM_NOACCESS(pointer, oldsize);
831  VALGRIND_MAKE_MEM_DEFINED(pointer, size);
832 #endif
833 
834  /* Disallow access to the chunk header. */
836 
837  return pointer;
838  }
839 
840  /* allocate new chunk */
841  newPointer = GenerationAlloc((MemoryContext) set, size);
842 
843  /* leave immediately if request was not completed */
844  if (newPointer == NULL)
845  {
846  /* Disallow access to the chunk header. */
848  return NULL;
849  }
850 
851  /*
852  * GenerationAlloc() may have returned a region that is still NOACCESS.
853  * Change it to UNDEFINED for the moment; memcpy() will then transfer
854  * definedness from the old allocation to the new. If we know the old
855  * allocation, copy just that much. Otherwise, make the entire old chunk
856  * defined to avoid errors as we copy the currently-NOACCESS trailing
857  * bytes.
858  */
859  VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size);
860 #ifdef MEMORY_CONTEXT_CHECKING
861  oldsize = chunk->requested_size;
862 #else
863  VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
864 #endif
865 
866  /* transfer existing data (certain to fit) */
867  memcpy(newPointer, pointer, oldsize);
868 
869  /* free old chunk */
870  GenerationFree(pointer);
871 
872  return newPointer;
873 }
874 
875 /*
876  * GenerationGetChunkContext
877  * Return the MemoryContext that 'pointer' belongs to.
878  */
881 {
882  MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
883  GenerationBlock *block;
884 
885  /* Allow access to the chunk header. */
887 
888  if (MemoryChunkIsExternal(chunk))
889  block = ExternalChunkGetBlock(chunk);
890  else
891  block = (GenerationBlock *) MemoryChunkGetBlock(chunk);
892 
893  /* Disallow access to the chunk header. */
895 
897  return &block->context->header;
898 }
899 
900 /*
901  * GenerationGetChunkSpace
902  * Given a currently-allocated chunk, determine the total space
903  * it occupies (including all memory-allocation overhead).
904  */
905 Size
907 {
908  MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
909  Size chunksize;
910 
911  /* Allow access to the chunk header. */
913 
914  if (MemoryChunkIsExternal(chunk))
915  {
916  GenerationBlock *block = ExternalChunkGetBlock(chunk);
917 
919  chunksize = block->endptr - (char *) pointer;
920  }
921  else
922  chunksize = MemoryChunkGetValue(chunk);
923 
924  /* Disallow access to the chunk header. */
926 
927  return Generation_CHUNKHDRSZ + chunksize;
928 }
929 
930 /*
931  * GenerationIsEmpty
932  * Is a GenerationContext empty of any allocated space?
933  */
934 bool
936 {
937  GenerationContext *set = (GenerationContext *) context;
938  dlist_iter iter;
939 
941 
942  dlist_foreach(iter, &set->blocks)
943  {
944  GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
945 
946  if (block->nchunks > 0)
947  return false;
948  }
949 
950  return true;
951 }
952 
953 /*
954  * GenerationStats
955  * Compute stats about memory consumption of a Generation context.
956  *
957  * printfunc: if not NULL, pass a human-readable stats string to this.
958  * passthru: pass this pointer through to printfunc.
959  * totals: if not NULL, add stats about this context into *totals.
960  * print_to_stderr: print stats to stderr if true, elog otherwise.
961  *
962  * XXX freespace only accounts for empty space at the end of the block, not
963  * space of freed chunks (which is unknown).
964  */
965 void
967  MemoryStatsPrintFunc printfunc, void *passthru,
968  MemoryContextCounters *totals, bool print_to_stderr)
969 {
970  GenerationContext *set = (GenerationContext *) context;
971  Size nblocks = 0;
972  Size nchunks = 0;
973  Size nfreechunks = 0;
974  Size totalspace;
975  Size freespace = 0;
976  dlist_iter iter;
977 
979 
980  /* Include context header in totalspace */
981  totalspace = MAXALIGN(sizeof(GenerationContext));
982 
983  dlist_foreach(iter, &set->blocks)
984  {
985  GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
986 
987  nblocks++;
988  nchunks += block->nchunks;
989  nfreechunks += block->nfree;
990  totalspace += block->blksize;
991  freespace += (block->endptr - block->freeptr);
992  }
993 
994  if (printfunc)
995  {
996  char stats_string[200];
997 
998  snprintf(stats_string, sizeof(stats_string),
999  "%zu total in %zu blocks (%zu chunks); %zu free (%zu chunks); %zu used",
1000  totalspace, nblocks, nchunks, freespace,
1001  nfreechunks, totalspace - freespace);
1002  printfunc(context, passthru, stats_string, print_to_stderr);
1003  }
1004 
1005  if (totals)
1006  {
1007  totals->nblocks += nblocks;
1008  totals->freechunks += nfreechunks;
1009  totals->totalspace += totalspace;
1010  totals->freespace += freespace;
1011  }
1012 }
1013 
1014 
1015 #ifdef MEMORY_CONTEXT_CHECKING
1016 
1017 /*
1018  * GenerationCheck
1019  * Walk through chunks and check consistency of memory.
1020  *
1021  * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll
1022  * find yourself in an infinite loop when trouble occurs, because this
1023  * routine will be entered again when elog cleanup tries to release memory!
1024  */
1025 void
1026 GenerationCheck(MemoryContext context)
1027 {
1028  GenerationContext *gen = (GenerationContext *) context;
1029  const char *name = context->name;
1030  dlist_iter iter;
1031  Size total_allocated = 0;
1032 
1033  /* walk all blocks in this context */
1034  dlist_foreach(iter, &gen->blocks)
1035  {
1036  GenerationBlock *block = dlist_container(GenerationBlock, node, iter.cur);
1037  int nfree,
1038  nchunks;
1039  char *ptr;
1040  bool has_external_chunk = false;
1041 
1042  total_allocated += block->blksize;
1043 
1044  /*
1045  * nfree > nchunks is surely wrong. Equality is allowed as the block
1046  * might completely empty if it's the freeblock.
1047  */
1048  if (block->nfree > block->nchunks)
1049  elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p exceeds %d allocated",
1050  name, block->nfree, block, block->nchunks);
1051 
1052  /* check block belongs to the correct context */
1053  if (block->context != gen)
1054  elog(WARNING, "problem in Generation %s: bogus context link in block %p",
1055  name, block);
1056 
1057  /* Now walk through the chunks and count them. */
1058  nfree = 0;
1059  nchunks = 0;
1060  ptr = ((char *) block) + Generation_BLOCKHDRSZ;
1061 
1062  while (ptr < block->freeptr)
1063  {
1064  MemoryChunk *chunk = (MemoryChunk *) ptr;
1065  GenerationBlock *chunkblock;
1066  Size chunksize;
1067 
1068  /* Allow access to the chunk header. */
1070 
1071  if (MemoryChunkIsExternal(chunk))
1072  {
1073  chunkblock = ExternalChunkGetBlock(chunk);
1074  chunksize = block->endptr - (char *) MemoryChunkGetPointer(chunk);
1075  has_external_chunk = true;
1076  }
1077  else
1078  {
1079  chunkblock = MemoryChunkGetBlock(chunk);
1080  chunksize = MemoryChunkGetValue(chunk);
1081  }
1082 
1083  /* move to the next chunk */
1084  ptr += (chunksize + Generation_CHUNKHDRSZ);
1085 
1086  nchunks += 1;
1087 
1088  /* chunks have both block and context pointers, so check both */
1089  if (chunkblock != block)
1090  elog(WARNING, "problem in Generation %s: bogus block link in block %p, chunk %p",
1091  name, block, chunk);
1092 
1093 
1094  /* is chunk allocated? */
1095  if (chunk->requested_size != InvalidAllocSize)
1096  {
1097  /* now make sure the chunk size is correct */
1098  if (chunksize < chunk->requested_size ||
1099  chunksize != MAXALIGN(chunksize))
1100  elog(WARNING, "problem in Generation %s: bogus chunk size in block %p, chunk %p",
1101  name, block, chunk);
1102 
1103  /* check sentinel */
1104  Assert(chunk->requested_size < chunksize);
1105  if (!sentinel_ok(chunk, Generation_CHUNKHDRSZ + chunk->requested_size))
1106  elog(WARNING, "problem in Generation %s: detected write past chunk end in block %p, chunk %p",
1107  name, block, chunk);
1108  }
1109  else
1110  nfree += 1;
1111 
1112  /* if chunk is allocated, disallow access to the chunk header */
1113  if (chunk->requested_size != InvalidAllocSize)
1115  }
1116 
1117  /*
1118  * Make sure we got the expected number of allocated and free chunks
1119  * (as tracked in the block header).
1120  */
1121  if (nchunks != block->nchunks)
1122  elog(WARNING, "problem in Generation %s: number of allocated chunks %d in block %p does not match header %d",
1123  name, nchunks, block, block->nchunks);
1124 
1125  if (nfree != block->nfree)
1126  elog(WARNING, "problem in Generation %s: number of free chunks %d in block %p does not match header %d",
1127  name, nfree, block, block->nfree);
1128 
1129  if (has_external_chunk && nchunks > 1)
1130  elog(WARNING, "problem in Generation %s: external chunk on non-dedicated block %p",
1131  name, block);
1132 
1133  }
1134 
1135  Assert(total_allocated == context->mem_allocated);
1136 }
1137 
1138 #endif /* MEMORY_CONTEXT_CHECKING */
unsigned int uint32
Definition: c.h:495
#define Min(x, y)
Definition: c.h:993
#define MAXALIGN(LEN)
Definition: c.h:800
#define Max(x, y)
Definition: c.h:987
#define StaticAssertDecl(condition, errmessage)
Definition: c.h:925
size_t Size
Definition: c.h:594
int errdetail(const char *fmt,...)
Definition: elog.c:1202
int errcode(int sqlerrcode)
Definition: elog.c:858
int errmsg(const char *fmt,...)
Definition: elog.c:1069
#define WARNING
Definition: elog.h:36
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:149
static void GenerationBlockInit(GenerationContext *context, GenerationBlock *block, Size blksize)
Definition: generation.c:533
#define IsKeeperBlock(set, block)
Definition: generation.c:128
#define Generation_CHUNK_FRACTION
Definition: generation.c:49
void GenerationReset(MemoryContext context)
Definition: generation.c:278
static Size GenerationBlockFreeBytes(GenerationBlock *block)
Definition: generation.c:588
#define KeeperBlock(set)
Definition: generation.c:123
static void GenerationBlockFree(GenerationContext *set, GenerationBlock *block)
Definition: generation.c:598
void GenerationFree(void *pointer)
Definition: generation.c:623
MemoryContext GenerationGetChunkContext(void *pointer)
Definition: generation.c:880
Size GenerationGetChunkSpace(void *pointer)
Definition: generation.c:906
void * GenerationPointer
Definition: generation.c:53
struct GenerationContext GenerationContext
#define Generation_CHUNKHDRSZ
Definition: generation.c:47
void * GenerationAlloc(MemoryContext context, Size size)
Definition: generation.c:345
static void GenerationBlockMarkEmpty(GenerationBlock *block)
Definition: generation.c:564
#define GenerationBlockIsValid(block)
Definition: generation.c:111
bool GenerationIsEmpty(MemoryContext context)
Definition: generation.c:935
void GenerationStats(MemoryContext context, MemoryStatsPrintFunc printfunc, void *passthru, MemoryContextCounters *totals, bool print_to_stderr)
Definition: generation.c:966
#define Generation_BLOCKHDRSZ
Definition: generation.c:46
MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, Size minContextSize, Size initBlockSize, Size maxBlockSize)
Definition: generation.c:157
static bool GenerationBlockIsEmpty(GenerationBlock *block)
Definition: generation.c:554
void * GenerationRealloc(void *pointer, Size size)
Definition: generation.c:738
void GenerationDelete(MemoryContext context)
Definition: generation.c:323
#define GenerationIsValid(set)
Definition: generation.c:104
#define ExternalChunkGetBlock(chunk)
Definition: generation.c:119
#define free(a)
Definition: header.h:65
#define malloc(a)
Definition: header.h:50
#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
#define dlist_foreach_modify(iter, lhead)
Definition: ilist.h:640
static bool dlist_is_empty(const dlist_head *head)
Definition: ilist.h:336
static dlist_node * dlist_head_node(dlist_head *head)
Definition: ilist.h:565
#define dlist_container(type, membername, ptr)
Definition: ilist.h:593
Assert(fmt[strlen(fmt) - 1] !='\n')
void MemoryContextCreate(MemoryContext node, NodeTag tag, MemoryContextMethodID method_id, MemoryContext parent, const char *name)
Definition: mcxt.c:973
MemoryContext TopMemoryContext
Definition: mcxt.c:141
void MemoryContextStats(MemoryContext context)
Definition: mcxt.c:699
#define VALGRIND_MAKE_MEM_DEFINED(addr, size)
Definition: memdebug.h:26
#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
@ 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 MemoryChunkSetHdrMaskExternal(MemoryChunk *chunk, MemoryContextMethodID methodid)
static void * MemoryChunkGetBlock(MemoryChunk *chunk)
#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
Definition: pg_bitutils.h:339
#define snprintf
Definition: port.h:238
char * freeptr
Definition: generation.c:96
dlist_node node
Definition: generation.c:91
GenerationContext * context
Definition: generation.c:92
MemoryContextData header
Definition: generation.c:61
GenerationBlock * freeblock
Definition: generation.c:72
uint32 maxBlockSize
Definition: generation.c:65
uint32 nextBlockSize
Definition: generation.c:66
dlist_head blocks
Definition: generation.c:74
uint32 initBlockSize
Definition: generation.c:64
GenerationBlock * block
Definition: generation.c:69
uint32 allocChunkLimit
Definition: generation.c:67
Size mem_allocated
Definition: memnodes.h:87
const char * name
Definition: memnodes.h:93
dlist_node * cur
Definition: ilist.h:179
dlist_node * cur
Definition: ilist.h:200
const char * name