summaryrefslogtreecommitdiff
path: root/src/backend/storage/freespace/freespace.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/storage/freespace/freespace.c')
-rw-r--r--src/backend/storage/freespace/freespace.c1115
1 files changed, 0 insertions, 1115 deletions
diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
deleted file mode 100644
index 2c0eb3ced83..00000000000
--- a/src/backend/storage/freespace/freespace.c
+++ /dev/null
@@ -1,1115 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * freespace.c
- * POSTGRES free space map for quickly finding free space in relations
- *
- *
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/freespace/freespace.c,v 1.12 2002/06/20 20:29:34 momjian Exp $
- *
- *
- * NOTES:
- *
- * The only really interesting aspect of this code is the heuristics for
- * deciding how much information we can afford to keep about each relation,
- * given that we have a limited amount of workspace in shared memory.
- * These currently work as follows:
- *
- * The number of distinct relations tracked is limited by a configuration
- * variable (MaxFSMRelations). When this would be exceeded, we discard the
- * least frequently used relation. A previously-unknown relation is always
- * entered into the map with useCount 1 on first reference, even if this
- * causes an existing entry with higher useCount to be discarded. This may
- * cause a little bit of thrashing among the bottom entries in the list,
- * but if we didn't do it then there'd be no way for a relation not in the
- * map to get in once the map is full. Note we allow a relation to be in the
- * map even if no pages are currently stored for it: this allows us to track
- * its useCount & threshold, which may eventually go high enough to give it
- * priority for page storage.
- *
- * The total number of pages tracked is also set by a configuration variable
- * (MaxFSMPages). We allocate these dynamically among the known relations,
- * giving preference to the more-frequently-referenced relations and those
- * with smaller tuples. This is done by a thresholding mechanism: for each
- * relation we keep track of a current target threshold, and allow only pages
- * with free space >= threshold to be stored in the map. The threshold starts
- * at BLCKSZ/2 (a somewhat arbitrary choice) for a newly entered relation.
- * On each GetFreeSpace reference, the threshold is moved towards the
- * request size using a slow exponential moving average; this means that
- * for heavily used relations, the threshold will approach the average
- * freespace request size (average tuple size). Whenever we run out of
- * storage space in the map, we double the threshold of all existing relations
- * (but not to more than BLCKSZ, so as to prevent runaway thresholds).
- * Infrequently used relations will thus tend to have large thresholds.
- *
- * XXX this thresholding mechanism is experimental; need to see how well
- * it works in practice.
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include <limits.h>
-
-#include "storage/freespace.h"
-#include "storage/itemid.h"
-#include "storage/lwlock.h"
-#include "storage/shmem.h"
-
-
-/*
- * Shared free-space-map objects
- *
- * Note: we handle pointers to these items as pointers, not as SHMEM_OFFSETs.
- * This assumes that all processes accessing the map will have the shared
- * memory segment mapped at the same place in their address space.
- */
-typedef struct FSMHeader FSMHeader;
-typedef struct FSMRelation FSMRelation;
-typedef struct FSMChunk FSMChunk;
-
-/* Header for whole map */
-struct FSMHeader
-{
- HTAB *relHash; /* hashtable of FSMRelation entries */
- FSMRelation *relList; /* FSMRelations in useCount order */
- FSMRelation *relListTail; /* tail of FSMRelation list */
- int numRels; /* number of FSMRelations now in use */
- FSMChunk *freeChunks; /* linked list of currently-free chunks */
- int numFreeChunks; /* number of free chunks */
-};
-
-/*
- * Per-relation struct --- this is an entry in the shared hash table.
- * The hash key is the RelFileNode value (hence, we look at the physical
- * relation ID, not the logical ID, which is appropriate).
- */
-struct FSMRelation
-{
- RelFileNode key; /* hash key (must be first) */
- FSMRelation *nextRel; /* next rel in useCount order */
- FSMRelation *priorRel; /* prior rel in useCount order */
- int useCount; /* use count for prioritizing rels */
- Size threshold; /* minimum amount of free space to keep */
- int nextPage; /* index (from 0) to start next search at */
- int numPages; /* total number of pages we have info
- * about */
- int numChunks; /* number of FSMChunks allocated to rel */
- FSMChunk *relChunks; /* linked list of page info chunks */
-};
-
-/*
- * Info about individual pages in a relation is stored in chunks to reduce
- * allocation overhead. Note that we allow any chunk of a relation's list
- * to be partly full, not only the last chunk; this speeds deletion of
- * individual page entries. When the total number of unused slots reaches
- * CHUNKPAGES, we compact out the unused slots so that we can return a chunk
- * to the freelist; but there's no point in doing the compaction before that.
- */
-
-#define CHUNKPAGES 32 /* each chunk can store this many pages */
-
-struct FSMChunk
-{
- FSMChunk *next; /* linked-list link */
- int numPages; /* number of pages described here */
- BlockNumber pages[CHUNKPAGES]; /* page numbers within relation */
- ItemLength bytes[CHUNKPAGES]; /* free space available on each
- * page */
-};
-
-
-int MaxFSMRelations; /* these are set by guc.c */
-int MaxFSMPages;
-
-static FSMHeader *FreeSpaceMap; /* points to FSMHeader in shared memory */
-
-
-static FSMRelation *lookup_fsm_rel(RelFileNode *rel);
-static FSMRelation *create_fsm_rel(RelFileNode *rel);
-static void delete_fsm_rel(FSMRelation *fsmrel);
-static void link_fsm_rel_after(FSMRelation *fsmrel, FSMRelation *oldrel);
-static void unlink_fsm_rel(FSMRelation *fsmrel);
-static void free_chunk_chain(FSMChunk *fchunk);
-static BlockNumber find_free_space(FSMRelation *fsmrel, Size spaceNeeded);
-static void fsm_record_free_space(FSMRelation *fsmrel, BlockNumber page,
- Size spaceAvail);
-static bool lookup_fsm_page_entry(FSMRelation *fsmrel, BlockNumber page,
- FSMChunk **outChunk, int *outChunkRelIndex);
-static bool insert_fsm_page_entry(FSMRelation *fsmrel,
- BlockNumber page, Size spaceAvail,
- FSMChunk *chunk, int chunkRelIndex);
-static bool push_fsm_page_entry(BlockNumber page, Size spaceAvail,
- FSMChunk *chunk, int chunkRelIndex);
-static void delete_fsm_page_entry(FSMRelation *fsmrel, FSMChunk *chunk,
- int chunkRelIndex);
-static void compact_fsm_page_list(FSMRelation *fsmrel);
-static void acquire_fsm_free_space(void);
-
-
-/*
- * Exported routines
- */
-
-
-/*
- * InitFreeSpaceMap -- Initialize the freespace module.
- *
- * This must be called once during shared memory initialization.
- * It builds the empty free space map table. FreeSpaceLock must also be
- * initialized at some point, but is not touched here --- we assume there is
- * no need for locking, since only the calling process can be accessing shared
- * memory as yet.
- */
-void
-InitFreeSpaceMap(void)
-{
- HASHCTL info;
- FSMChunk *chunks,
- *prevchunk;
- int nchunks;
-
- /* Create table header */
- FreeSpaceMap = (FSMHeader *) ShmemAlloc(sizeof(FSMHeader));
- if (FreeSpaceMap == NULL)
- elog(FATAL, "Insufficient shared memory for free space map");
- MemSet(FreeSpaceMap, 0, sizeof(FSMHeader));
-
- /* Create hashtable for FSMRelations */
- info.keysize = sizeof(RelFileNode);
- info.entrysize = sizeof(FSMRelation);
- info.hash = tag_hash;
-
- FreeSpaceMap->relHash = ShmemInitHash("Free Space Map Hash",
- MaxFSMRelations / 10,
- MaxFSMRelations,
- &info,
- (HASH_ELEM | HASH_FUNCTION));
-
- if (!FreeSpaceMap->relHash)
- elog(FATAL, "Insufficient shared memory for free space map");
-
- /* Allocate FSMChunks and fill up the free-chunks list */
- nchunks = (MaxFSMPages - 1) / CHUNKPAGES + 1;
- FreeSpaceMap->numFreeChunks = nchunks;
-
- chunks = (FSMChunk *) ShmemAlloc(nchunks * sizeof(FSMChunk));
- if (chunks == NULL)
- elog(FATAL, "Insufficient shared memory for free space map");
-
- prevchunk = NULL;
- while (nchunks-- > 0)
- {
- chunks->next = prevchunk;
- prevchunk = chunks;
- chunks++;
- }
- FreeSpaceMap->freeChunks = prevchunk;
-}
-
-/*
- * Estimate amount of shmem space needed for FSM.
- */
-int
-FreeSpaceShmemSize(void)
-{
- int size;
- int nchunks;
-
- /* table header */
- size = MAXALIGN(sizeof(FSMHeader));
-
- /* hash table, including the FSMRelation objects */
- size += hash_estimate_size(MaxFSMRelations, sizeof(FSMRelation));
-
- /* FSMChunk objects */
- nchunks = (MaxFSMPages - 1) / CHUNKPAGES + 1;
-
- size += MAXALIGN(nchunks * sizeof(FSMChunk));
-
- return size;
-}
-
-/*
- * GetPageWithFreeSpace - try to find a page in the given relation with
- * at least the specified amount of free space.
- *
- * If successful, return the block number; if not, return InvalidBlockNumber.
- *
- * The caller must be prepared for the possibility that the returned page
- * will turn out to have too little space available by the time the caller
- * gets a lock on it. In that case, the caller should report the actual
- * amount of free space available on that page (via RecordFreeSpace) and
- * then try again. If InvalidBlockNumber is returned, extend the relation.
- */
-BlockNumber
-GetPageWithFreeSpace(RelFileNode *rel, Size spaceNeeded)
-{
- FSMRelation *fsmrel;
- BlockNumber freepage;
-
- LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
-
- /*
- * We always add a rel to the hashtable when it is inquired about.
- */
- fsmrel = create_fsm_rel(rel);
-
- /*
- * Adjust the threshold towards the space request. This essentially
- * implements an exponential moving average with an equivalent period
- * of about 63 requests. Ignore silly requests, however, to ensure
- * that the average stays in bounds.
- *
- * In theory, if the threshold increases here we should immediately
- * delete any pages that fall below the new threshold. In practice it
- * seems OK to wait until we have a need to compact space.
- */
- if (spaceNeeded > 0 && spaceNeeded < BLCKSZ)
- {
- int cur_avg = (int) fsmrel->threshold;
-
- cur_avg += ((int) spaceNeeded - cur_avg) / 32;
- fsmrel->threshold = (Size) cur_avg;
- }
- freepage = find_free_space(fsmrel, spaceNeeded);
- LWLockRelease(FreeSpaceLock);
- return freepage;
-}
-
-/*
- * RecordFreeSpace - record the amount of free space available on a page.
- *
- * The FSM is at liberty to forget about the page instead, if the amount of
- * free space is too small to be interesting. The only guarantee is that
- * a subsequent request to get a page with more than spaceAvail space free
- * will not return this page.
- */
-void
-RecordFreeSpace(RelFileNode *rel, BlockNumber page, Size spaceAvail)
-{
- FSMRelation *fsmrel;
-
- /* Sanity check: ensure spaceAvail will fit into ItemLength */
- AssertArg(spaceAvail < BLCKSZ);
-
- LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
-
- /*
- * We choose not to add rels to the hashtable unless they've been
- * inquired about with GetPageWithFreeSpace. Also, a Record operation
- * does not increase the useCount or change threshold, only Get does.
- */
- fsmrel = lookup_fsm_rel(rel);
- if (fsmrel)
- fsm_record_free_space(fsmrel, page, spaceAvail);
- LWLockRelease(FreeSpaceLock);
-}
-
-/*
- * RecordAndGetPageWithFreeSpace - combo form to save one lock and
- * hash table lookup cycle.
- */
-BlockNumber
-RecordAndGetPageWithFreeSpace(RelFileNode *rel,
- BlockNumber oldPage,
- Size oldSpaceAvail,
- Size spaceNeeded)
-{
- FSMRelation *fsmrel;
- BlockNumber freepage;
-
- /* Sanity check: ensure spaceAvail will fit into ItemLength */
- AssertArg(oldSpaceAvail < BLCKSZ);
-
- LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
-
- /*
- * We always add a rel to the hashtable when it is inquired about.
- */
- fsmrel = create_fsm_rel(rel);
-
- /*
- * Adjust the threshold towards the space request, same as in
- * GetPageWithFreeSpace.
- *
- * Note that we do this before storing data for oldPage, which means this
- * isn't exactly equivalent to Record followed by Get; but it seems
- * appropriate to adjust the threshold first.
- */
- if (spaceNeeded > 0 && spaceNeeded < BLCKSZ)
- {
- int cur_avg = (int) fsmrel->threshold;
-
- cur_avg += ((int) spaceNeeded - cur_avg) / 32;
- fsmrel->threshold = (Size) cur_avg;
- }
- /* Do the Record */
- fsm_record_free_space(fsmrel, oldPage, oldSpaceAvail);
- /* Do the Get */
- freepage = find_free_space(fsmrel, spaceNeeded);
- LWLockRelease(FreeSpaceLock);
- return freepage;
-}
-
-/*
- * MultiRecordFreeSpace - record available-space info about multiple pages
- * of a relation in one call.
- *
- * First, if minPage <= maxPage, the FSM must discard any entries it has for
- * pages in that page number range (inclusive). This allows obsolete info
- * to be discarded. Second, if nPages > 0, record the page numbers and free
- * space amounts in the given arrays. As with RecordFreeSpace, the FSM is at
- * liberty to discard some of the information. However, it *must* discard
- * previously stored info in the minPage..maxPage range (for example, this
- * case is used to remove info about deleted pages during relation truncation).
- */
-void
-MultiRecordFreeSpace(RelFileNode *rel,
- BlockNumber minPage,
- BlockNumber maxPage,
- int nPages,
- BlockNumber *pages,
- Size *spaceAvail)
-{
- FSMRelation *fsmrel;
- int i;
-
- LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
- fsmrel = lookup_fsm_rel(rel);
- if (fsmrel)
- {
- /*
- * Remove entries in specified range
- */
- if (minPage <= maxPage)
- {
- FSMChunk *chunk;
- int chunkRelIndex;
- bool done;
-
- /* Use lookup to locate first entry >= minPage */
- lookup_fsm_page_entry(fsmrel, minPage, &chunk, &chunkRelIndex);
- /* Set free space to 0 for each page within range */
- done = false;
- while (chunk && !done)
- {
- int numPages = chunk->numPages;
-
- for (; chunkRelIndex < numPages; chunkRelIndex++)
- {
- if (chunk->pages[chunkRelIndex] > maxPage)
- {
- done = true;
- break;
- }
- chunk->bytes[chunkRelIndex] = 0;
- }
- chunk = chunk->next;
- chunkRelIndex = 0;
- }
- /* Now compact out the zeroed entries */
- compact_fsm_page_list(fsmrel);
- }
-
- /*
- * Add new entries, if appropriate.
- *
- * XXX we could probably be smarter about this than doing it
- * completely separately for each one. FIXME later.
- *
- * One thing we can do is short-circuit the process entirely if a
- * page (a) has too little free space to be recorded, and (b) is
- * within the minPage..maxPage range --- then we deleted any old
- * entry above, and we aren't going to make a new one. This is
- * particularly useful since in most cases, all the passed pages
- * will in fact be in the minPage..maxPage range.
- */
- for (i = 0; i < nPages; i++)
- {
- BlockNumber page = pages[i];
- Size avail = spaceAvail[i];
-
- if (avail >= fsmrel->threshold ||
- page < minPage || page > maxPage)
- fsm_record_free_space(fsmrel, page, avail);
- }
- }
- LWLockRelease(FreeSpaceLock);
-}
-
-/*
- * FreeSpaceMapForgetRel - forget all about a relation.
- *
- * This is called when a relation is deleted. Although we could just let
- * the rel age out of the map, it's better to reclaim and reuse the space
- * sooner.
- */
-void
-FreeSpaceMapForgetRel(RelFileNode *rel)
-{
- FSMRelation *fsmrel;
-
- LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
- fsmrel = lookup_fsm_rel(rel);
- if (fsmrel)
- delete_fsm_rel(fsmrel);
- LWLockRelease(FreeSpaceLock);
-}
-
-/*
- * FreeSpaceMapForgetDatabase - forget all relations of a database.
- *
- * This is called during DROP DATABASE. As above, might as well reclaim
- * map space sooner instead of later.
- *
- * XXX when we implement tablespaces, target Oid will need to be tablespace
- * ID not database ID.
- */
-void
-FreeSpaceMapForgetDatabase(Oid dbid)
-{
- FSMRelation *fsmrel,
- *nextrel;
-
- LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
- for (fsmrel = FreeSpaceMap->relList; fsmrel; fsmrel = nextrel)
- {
- nextrel = fsmrel->nextRel; /* in case we delete it */
- if (fsmrel->key.tblNode == dbid)
- delete_fsm_rel(fsmrel);
- }
- LWLockRelease(FreeSpaceLock);
-}
-
-
-/*
- * Internal routines. These all assume the caller holds the FreeSpaceLock.
- */
-
-/*
- * Lookup a relation in the hash table. If not present, return NULL.
- * The relation's useCount is not changed.
- */
-static FSMRelation *
-lookup_fsm_rel(RelFileNode *rel)
-{
- FSMRelation *fsmrel;
-
- fsmrel = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
- (void *) rel,
- HASH_FIND,
- NULL);
- if (!fsmrel)
- return NULL;
-
- return fsmrel;
-}
-
-/*
- * Lookup a relation in the hash table, creating an entry if not present.
- *
- * On successful lookup, the relation's useCount is incremented, possibly
- * causing it to move up in the priority list.
- */
-static FSMRelation *
-create_fsm_rel(RelFileNode *rel)
-{
- FSMRelation *fsmrel,
- *oldrel;
- bool found;
-
- fsmrel = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
- (void *) rel,
- HASH_ENTER,
- &found);
- if (!fsmrel)
- elog(ERROR, "FreeSpaceMap hashtable out of memory");
-
- if (!found)
- {
- /* New hashtable entry, initialize it (hash_search set the key) */
- fsmrel->useCount = 1;
- fsmrel->threshold = BLCKSZ / 2; /* starting point for new entry */
- fsmrel->nextPage = 0;
- fsmrel->numPages = 0;
- fsmrel->numChunks = 0;
- fsmrel->relChunks = NULL;
- /* Discard lowest-priority existing rel, if we are over limit */
- if (FreeSpaceMap->numRels >= MaxFSMRelations)
- delete_fsm_rel(FreeSpaceMap->relListTail);
-
- /*
- * Add new entry in front of any others with useCount 1 (since it
- * is more recently used than them).
- */
- oldrel = FreeSpaceMap->relListTail;
- while (oldrel && oldrel->useCount <= 1)
- oldrel = oldrel->priorRel;
- link_fsm_rel_after(fsmrel, oldrel);
- FreeSpaceMap->numRels++;
- }
- else
- {
- int myCount;
-
- /* Existing entry, advance its useCount */
- if (++(fsmrel->useCount) >= INT_MAX / 2)
- {
- /* When useCounts threaten to overflow, reduce 'em all 2X */
- for (oldrel = FreeSpaceMap->relList;
- oldrel != NULL;
- oldrel = oldrel->nextRel)
- oldrel->useCount >>= 1;
- }
- /* If warranted, move it up the priority list */
- oldrel = fsmrel->priorRel;
- myCount = fsmrel->useCount;
- if (oldrel && oldrel->useCount <= myCount)
- {
- unlink_fsm_rel(fsmrel);
- while (oldrel && oldrel->useCount <= myCount)
- oldrel = oldrel->priorRel;
- link_fsm_rel_after(fsmrel, oldrel);
- }
- }
-
- return fsmrel;
-}
-
-/*
- * Remove an existing FSMRelation entry.
- */
-static void
-delete_fsm_rel(FSMRelation *fsmrel)
-{
- FSMRelation *result;
-
- free_chunk_chain(fsmrel->relChunks);
- unlink_fsm_rel(fsmrel);
- FreeSpaceMap->numRels--;
- result = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
- (void *) &(fsmrel->key),
- HASH_REMOVE,
- NULL);
- if (!result)
- elog(ERROR, "FreeSpaceMap hashtable corrupted");
-}
-
-/*
- * Link a FSMRelation into the priority list just after the given existing
- * entry (or at the head of the list, if oldrel is NULL).
- */
-static void
-link_fsm_rel_after(FSMRelation *fsmrel, FSMRelation *oldrel)
-{
- if (oldrel == NULL)
- {
- /* insert at head */
- fsmrel->priorRel = NULL;
- fsmrel->nextRel = FreeSpaceMap->relList;
- FreeSpaceMap->relList = fsmrel;
- if (fsmrel->nextRel != NULL)
- fsmrel->nextRel->priorRel = fsmrel;
- else
- FreeSpaceMap->relListTail = fsmrel;
- }
- else
- {
- /* insert after oldrel */
- fsmrel->priorRel = oldrel;
- fsmrel->nextRel = oldrel->nextRel;
- oldrel->nextRel = fsmrel;
- if (fsmrel->nextRel != NULL)
- fsmrel->nextRel->priorRel = fsmrel;
- else
- FreeSpaceMap->relListTail = fsmrel;
- }
-}
-
-/*
- * Delink a FSMRelation from the priority list.
- */
-static void
-unlink_fsm_rel(FSMRelation *fsmrel)
-{
- if (fsmrel->priorRel != NULL)
- fsmrel->priorRel->nextRel = fsmrel->nextRel;
- else
- FreeSpaceMap->relList = fsmrel->nextRel;
- if (fsmrel->nextRel != NULL)
- fsmrel->nextRel->priorRel = fsmrel->priorRel;
- else
- FreeSpaceMap->relListTail = fsmrel->priorRel;
-}
-
-/*
- * Return all the FSMChunks in the chain starting at fchunk to the freelist.
- * (Caller must handle unlinking them from wherever they were.)
- */
-static void
-free_chunk_chain(FSMChunk *fchunk)
-{
- int nchunks;
- FSMChunk *lchunk;
-
- if (fchunk == NULL)
- return;
- nchunks = 1;
- lchunk = fchunk;
- while (lchunk->next != NULL)
- {
- nchunks++;
- lchunk = lchunk->next;
- }
- lchunk->next = FreeSpaceMap->freeChunks;
- FreeSpaceMap->freeChunks = fchunk;
- FreeSpaceMap->numFreeChunks += nchunks;
-}
-
-/*
- * Look to see if a page with at least the specified amount of space is
- * available in the given FSMRelation. If so, return its page number,
- * and advance the nextPage counter so that the next inquiry will return
- * a different page if possible. Return InvalidBlockNumber if no success.
- */
-static BlockNumber
-find_free_space(FSMRelation *fsmrel, Size spaceNeeded)
-{
- int pagesToCheck, /* outer loop counter */
- pageIndex, /* current page index relative to relation */
- chunkRelIndex; /* current page index relative to curChunk */
- FSMChunk *curChunk;
-
- pageIndex = fsmrel->nextPage;
- /* Last operation may have left nextPage pointing past end */
- if (pageIndex >= fsmrel->numPages)
- pageIndex = 0;
- curChunk = fsmrel->relChunks;
- chunkRelIndex = pageIndex;
-
- for (pagesToCheck = fsmrel->numPages; pagesToCheck > 0; pagesToCheck--)
- {
- /*
- * Make sure we are in the right chunk. First time through, we
- * may have to advance through multiple chunks; subsequent loops
- * should do this at most once.
- */
- while (chunkRelIndex >= curChunk->numPages)
- {
- chunkRelIndex -= curChunk->numPages;
- curChunk = curChunk->next;
- }
- /* Check the next page */
- if ((Size) curChunk->bytes[chunkRelIndex] >= spaceNeeded)
- {
- fsmrel->nextPage = pageIndex + 1;
- return curChunk->pages[chunkRelIndex];
- }
- /* Advance pageIndex and chunkRelIndex, wrapping around if needed */
- if (++pageIndex >= fsmrel->numPages)
- {
- pageIndex = 0;
- curChunk = fsmrel->relChunks;
- chunkRelIndex = 0;
- }
- else
- chunkRelIndex++;
- }
-
- return InvalidBlockNumber; /* nothing found */
-}
-
-/*
- * fsm_record_free_space - guts of RecordFreeSpace, which is also used by
- * RecordAndGetPageWithFreeSpace.
- */
-static void
-fsm_record_free_space(FSMRelation *fsmrel, BlockNumber page, Size spaceAvail)
-{
- FSMChunk *chunk;
- int chunkRelIndex;
-
- if (lookup_fsm_page_entry(fsmrel, page, &chunk, &chunkRelIndex))
- {
- /* Found an existing entry for page; update or delete it */
- if (spaceAvail >= fsmrel->threshold)
- chunk->bytes[chunkRelIndex] = (ItemLength) spaceAvail;
- else
- delete_fsm_page_entry(fsmrel, chunk, chunkRelIndex);
- }
- else
- {
- /*
- * No existing entry; add one if spaceAvail exceeds threshold.
- *
- * CORNER CASE: if we have to do acquire_fsm_free_space then our own
- * threshold will increase, possibly meaning that we shouldn't
- * store the page after all. Loop to redo the test if that
- * happens. The loop also covers the possibility that
- * acquire_fsm_free_space must be executed more than once to free
- * any space (ie, thresholds must be more than doubled).
- */
- while (spaceAvail >= fsmrel->threshold)
- {
- if (insert_fsm_page_entry(fsmrel, page, spaceAvail,
- chunk, chunkRelIndex))
- break;
- /* No space, acquire some and recheck threshold */
- acquire_fsm_free_space();
- if (spaceAvail < fsmrel->threshold)
- break;
-
- /*
- * Need to redo the lookup since our own page list may well
- * have lost entries, so position is not correct anymore.
- */
- if (lookup_fsm_page_entry(fsmrel, page,
- &chunk, &chunkRelIndex))
- elog(ERROR, "fsm_record_free_space: unexpected match");
- }
- }
-}
-
-/*
- * Look for an entry for a specific page (block number) in a FSMRelation.
- * Returns TRUE if a matching entry exists, else FALSE.
- *
- * The output arguments *outChunk, *outChunkRelIndex are set to indicate where
- * the entry exists (if TRUE result) or could be inserted (if FALSE result).
- * *chunk is set to NULL if there is no place to insert, ie, the entry would
- * need to be added to a new chunk.
- */
-static bool
-lookup_fsm_page_entry(FSMRelation *fsmrel, BlockNumber page,
- FSMChunk **outChunk, int *outChunkRelIndex)
-{
- FSMChunk *chunk;
- int chunkRelIndex;
-
- for (chunk = fsmrel->relChunks; chunk; chunk = chunk->next)
- {
- int numPages = chunk->numPages;
-
- /* Can skip the chunk quickly if page must be after last in chunk */
- if (numPages > 0 && page <= chunk->pages[numPages - 1])
- {
- for (chunkRelIndex = 0; chunkRelIndex < numPages; chunkRelIndex++)
- {
- if (page <= chunk->pages[chunkRelIndex])
- {
- *outChunk = chunk;
- *outChunkRelIndex = chunkRelIndex;
- return (page == chunk->pages[chunkRelIndex]);
- }
- }
- /* Should not get here, given above test */
- Assert(false);
- }
-
- /*
- * If we are about to fall off the end, and there's space
- * available in the end chunk, return a pointer to it.
- */
- if (chunk->next == NULL && numPages < CHUNKPAGES)
- {
- *outChunk = chunk;
- *outChunkRelIndex = numPages;
- return false;
- }
- }
-
- /*
- * Adding the page would require a new chunk (or, perhaps, compaction
- * of available free space --- not my problem here).
- */
- *outChunk = NULL;
- *outChunkRelIndex = 0;
- return false;
-}
-
-/*
- * Insert a new page entry into a FSMRelation's list at given position
- * (chunk == NULL implies at end).
- *
- * If there is no space available to insert the entry, return FALSE.
- */
-static bool
-insert_fsm_page_entry(FSMRelation *fsmrel, BlockNumber page, Size spaceAvail,
- FSMChunk *chunk, int chunkRelIndex)
-{
- /* Outer loop handles retry after compacting rel's page list */
- for (;;)
- {
- if (fsmrel->numPages >= fsmrel->numChunks * CHUNKPAGES)
- {
- /* No free space within chunk list, so need another chunk */
- FSMChunk *newChunk;
-
- if ((newChunk = FreeSpaceMap->freeChunks) == NULL)
- return false; /* can't do it */
- FreeSpaceMap->freeChunks = newChunk->next;
- FreeSpaceMap->numFreeChunks--;
- newChunk->next = NULL;
- newChunk->numPages = 0;
- if (fsmrel->relChunks == NULL)
- fsmrel->relChunks = newChunk;
- else
- {
- FSMChunk *priorChunk = fsmrel->relChunks;
-
- while (priorChunk->next != NULL)
- priorChunk = priorChunk->next;
- priorChunk->next = newChunk;
- }
- fsmrel->numChunks++;
- if (chunk == NULL)
- {
- /* Original search found that new page belongs at end */
- chunk = newChunk;
- chunkRelIndex = 0;
- }
- }
-
- /* Try to insert it the easy way, ie, just move down subsequent data */
- if (chunk &&
- push_fsm_page_entry(page, spaceAvail, chunk, chunkRelIndex))
- {
- fsmrel->numPages++;
- fsmrel->nextPage++; /* don't return same page twice running */
- return true;
- }
-
- /*
- * There is space available, but evidently it's before the place where
- * the page entry needs to go. Compact the list and try again. This
- * will require us to redo the search for the appropriate place.
- * Furthermore, compact_fsm_page_list deletes empty end chunks, so
- * we may need to repeat the action of grabbing a new end chunk.
- */
- compact_fsm_page_list(fsmrel);
- if (lookup_fsm_page_entry(fsmrel, page, &chunk, &chunkRelIndex))
- elog(ERROR, "insert_fsm_page_entry: entry already exists!");
- }
-}
-
-/*
- * Auxiliary routine for insert_fsm_page_entry: try to push entries to the
- * right to insert at chunk/chunkRelIndex. Return TRUE if successful.
- * Note that the FSMRelation's own fields are not updated.
- */
-static bool
-push_fsm_page_entry(BlockNumber page, Size spaceAvail,
- FSMChunk *chunk, int chunkRelIndex)
-{
- int i;
-
- if (chunk->numPages >= CHUNKPAGES)
- {
- if (chunk->next == NULL)
- return false; /* no space */
- /* try to push chunk's last item to next chunk */
- if (!push_fsm_page_entry(chunk->pages[CHUNKPAGES - 1],
- chunk->bytes[CHUNKPAGES - 1],
- chunk->next, 0))
- return false;
- /* successfully pushed it */
- chunk->numPages--;
- }
- for (i = chunk->numPages; i > chunkRelIndex; i--)
- {
- chunk->pages[i] = chunk->pages[i - 1];
- chunk->bytes[i] = chunk->bytes[i - 1];
- }
- chunk->numPages++;
- chunk->pages[chunkRelIndex] = page;
- chunk->bytes[chunkRelIndex] = (ItemLength) spaceAvail;
- return true;
-}
-
-/*
- * Delete one page entry from a FSMRelation's list. Compact free space
- * in the list, but only if a chunk can be returned to the freelist.
- */
-static void
-delete_fsm_page_entry(FSMRelation *fsmrel, FSMChunk *chunk, int chunkRelIndex)
-{
- int i,
- lim;
-
- Assert(chunk && chunkRelIndex >= 0 && chunkRelIndex < chunk->numPages);
- /* Compact out space in this chunk */
- lim = --chunk->numPages;
- for (i = chunkRelIndex; i < lim; i++)
- {
- chunk->pages[i] = chunk->pages[i + 1];
- chunk->bytes[i] = chunk->bytes[i + 1];
- }
- /* Compact the whole list if a chunk can be freed */
- fsmrel->numPages--;
- if (fsmrel->numPages <= (fsmrel->numChunks - 1) * CHUNKPAGES)
- compact_fsm_page_list(fsmrel);
-}
-
-/*
- * Remove any pages with free space less than the current threshold for the
- * FSMRelation, compact out free slots in non-last chunks, and return any
- * completely freed chunks to the freelist.
- */
-static void
-compact_fsm_page_list(FSMRelation *fsmrel)
-{
- Size threshold = fsmrel->threshold;
- FSMChunk *srcChunk,
- *dstChunk;
- int srcIndex,
- dstIndex,
- dstPages,
- dstChunkCnt;
-
- srcChunk = dstChunk = fsmrel->relChunks;
- srcIndex = dstIndex = 0;
- dstPages = 0; /* total pages kept */
- dstChunkCnt = 1; /* includes current dstChunk */
-
- while (srcChunk != NULL)
- {
- int srcPages = srcChunk->numPages;
-
- while (srcIndex < srcPages)
- {
- if ((Size) srcChunk->bytes[srcIndex] >= threshold)
- {
- if (dstIndex >= CHUNKPAGES)
- {
- /*
- * At this point srcChunk must be pointing to a later
- * chunk, so it's OK to overwrite dstChunk->numPages.
- */
- dstChunk->numPages = dstIndex;
- dstChunk = dstChunk->next;
- dstChunkCnt++;
- dstIndex = 0;
- }
- dstChunk->pages[dstIndex] = srcChunk->pages[srcIndex];
- dstChunk->bytes[dstIndex] = srcChunk->bytes[srcIndex];
- dstIndex++;
- dstPages++;
- }
- srcIndex++;
- }
- srcChunk = srcChunk->next;
- srcIndex = 0;
- }
-
- if (dstPages == 0)
- {
- /* No chunks to be kept at all */
- fsmrel->nextPage = 0;
- fsmrel->numPages = 0;
- fsmrel->numChunks = 0;
- fsmrel->relChunks = NULL;
- free_chunk_chain(dstChunk);
- }
- else
- {
- /* we deliberately don't change nextPage here */
- fsmrel->numPages = dstPages;
- fsmrel->numChunks = dstChunkCnt;
- dstChunk->numPages = dstIndex;
- free_chunk_chain(dstChunk->next);
- dstChunk->next = NULL;
- }
-}
-
-/*
- * Acquire some free space by raising the thresholds of all FSMRelations.
- *
- * Note there is no guarantee as to how much space will be freed by a single
- * invocation; caller may repeat if necessary.
- */
-static void
-acquire_fsm_free_space(void)
-{
- FSMRelation *fsmrel;
-
- for (fsmrel = FreeSpaceMap->relList; fsmrel; fsmrel = fsmrel->nextRel)
- {
- fsmrel->threshold *= 2;
- /* Limit thresholds to BLCKSZ so they can't get ridiculously large */
- if (fsmrel->threshold > BLCKSZ)
- fsmrel->threshold = BLCKSZ;
- /* Release any pages that don't meet the new threshold */
- compact_fsm_page_list(fsmrel);
- }
-}
-
-
-#ifdef FREESPACE_DEBUG
-/*
- * Dump contents of freespace map for debugging.
- *
- * We assume caller holds the FreeSpaceLock, or is otherwise unconcerned
- * about other processes.
- */
-void
-DumpFreeSpace(void)
-{
- FSMRelation *fsmrel;
- FSMRelation *prevrel = NULL;
- int relNum = 0;
- FSMChunk *chunk;
- int chunkRelIndex;
- int nChunks;
- int nPages;
-
- for (fsmrel = FreeSpaceMap->relList; fsmrel; fsmrel = fsmrel->nextRel)
- {
- relNum++;
- fprintf(stderr, "Map %d: rel %u/%u useCount %d threshold %u nextPage %d\nMap= ",
- relNum, fsmrel->key.tblNode, fsmrel->key.relNode,
- fsmrel->useCount, fsmrel->threshold, fsmrel->nextPage);
- nChunks = nPages = 0;
- for (chunk = fsmrel->relChunks; chunk; chunk = chunk->next)
- {
- int numPages = chunk->numPages;
-
- nChunks++;
- for (chunkRelIndex = 0; chunkRelIndex < numPages; chunkRelIndex++)
- {
- nPages++;
- fprintf(stderr, " %u:%u",
- chunk->pages[chunkRelIndex],
- chunk->bytes[chunkRelIndex]);
- }
- }
- fprintf(stderr, "\n");
- /* Cross-check local counters and list links */
- if (nPages != fsmrel->numPages)
- fprintf(stderr, "DumpFreeSpace: %d pages in rel, but numPages = %d\n",
- nPages, fsmrel->numPages);
- if (nChunks != fsmrel->numChunks)
- fprintf(stderr, "DumpFreeSpace: %d chunks in rel, but numChunks = %d\n",
- nChunks, fsmrel->numChunks);
- if (prevrel != fsmrel->priorRel)
- fprintf(stderr, "DumpFreeSpace: broken list links\n");
- prevrel = fsmrel;
- }
- if (prevrel != FreeSpaceMap->relListTail)
- fprintf(stderr, "DumpFreeSpace: broken list links\n");
- /* Cross-check global counters */
- if (relNum != FreeSpaceMap->numRels)
- fprintf(stderr, "DumpFreeSpace: %d rels in list, but numRels = %d\n",
- relNum, FreeSpaceMap->numRels);
- nChunks = 0;
- for (chunk = FreeSpaceMap->freeChunks; chunk; chunk = chunk->next)
- nChunks++;
- if (nChunks != FreeSpaceMap->numFreeChunks)
- fprintf(stderr, "DumpFreeSpace: %d chunks in list, but numFreeChunks = %d\n",
- nChunks, FreeSpaceMap->numFreeChunks);
-}
-
-#endif /* FREESPACE_DEBUG */