Logo Search packages:      
Sourcecode: postgresql-8.4 version File versions

localbuf.c

/*-------------------------------------------------------------------------
 *
 * localbuf.c
 *      local buffer manager. Fast buffer manager for temporary tables,
 *      which never need to be WAL-logged or checkpointed, etc.
 *
 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994-5, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      $PostgreSQL: pgsql/src/backend/storage/buffer/localbuf.c,v 1.87 2009/06/11 14:49:01 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/catalog.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/smgr.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/resowner.h"


/*#define LBDEBUG*/

/* entry for buffer lookup hashtable */
typedef struct
{
      BufferTag   key;              /* Tag of a disk page */
      int               id;                     /* Associated local buffer's index */
} LocalBufferLookupEnt;

/* Note: this macro only works on local buffers, not shared ones! */
#define LocalBufHdrGetBlock(bufHdr) \
      LocalBufferBlockPointers[-((bufHdr)->buf_id + 2)]

int               NLocBuffer = 0;         /* until buffers are initialized */

BufferDesc *LocalBufferDescriptors = NULL;
Block    *LocalBufferBlockPointers = NULL;
int32    *LocalRefCount = NULL;

static int  nextFreeLocalBuf = 0;

static HTAB *LocalBufHash = NULL;


static void InitLocalBuffers(void);
static Block GetLocalBufferStorage(void);


/*
 * LocalPrefetchBuffer -
 *      initiate asynchronous read of a block of a relation
 *
 * Do PrefetchBuffer's work for temporary relations.
 * No-op if prefetching isn't compiled in.
 */
void
LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum,
                              BlockNumber blockNum)
{
#ifdef USE_PREFETCH
      BufferTag   newTag;                 /* identity of requested block */
      LocalBufferLookupEnt *hresult;

      INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);

      /* Initialize local buffers if first request in this session */
      if (LocalBufHash == NULL)
            InitLocalBuffers();

      /* See if the desired buffer already exists */
      hresult = (LocalBufferLookupEnt *)
            hash_search(LocalBufHash, (void *) &newTag, HASH_FIND, NULL);

      if (hresult)
      {
            /* Yes, so nothing to do */
            return;
      }

      /* Not in buffers, so initiate prefetch */
      smgrprefetch(smgr, forkNum, blockNum);
#endif   /* USE_PREFETCH */
}


/*
 * LocalBufferAlloc -
 *      Find or create a local buffer for the given page of the given relation.
 *
 * API is similar to bufmgr.c's BufferAlloc, except that we do not need
 * to do any locking since this is all local.   Also, IO_IN_PROGRESS
 * does not get set.  Lastly, we support only default access strategy
 * (hence, usage_count is always advanced).
 */
BufferDesc *
LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
                         bool *foundPtr)
{
      BufferTag   newTag;                 /* identity of requested block */
      LocalBufferLookupEnt *hresult;
      BufferDesc *bufHdr;
      int               b;
      int               trycounter;
      bool        found;

      INIT_BUFFERTAG(newTag, smgr->smgr_rnode, forkNum, blockNum);

      /* Initialize local buffers if first request in this session */
      if (LocalBufHash == NULL)
            InitLocalBuffers();

      /* See if the desired buffer already exists */
      hresult = (LocalBufferLookupEnt *)
            hash_search(LocalBufHash, (void *) &newTag, HASH_FIND, NULL);

      if (hresult)
      {
            b = hresult->id;
            bufHdr = &LocalBufferDescriptors[b];
            Assert(BUFFERTAGS_EQUAL(bufHdr->tag, newTag));
#ifdef LBDEBUG
            fprintf(stderr, "LB ALLOC (%u,%d,%d) %d\n",
                        smgr->smgr_rnode.relNode, forkNum, blockNum, -b - 1);
#endif
            /* this part is equivalent to PinBuffer for a shared buffer */
            if (LocalRefCount[b] == 0)
            {
                  if (bufHdr->usage_count < BM_MAX_USAGE_COUNT)
                        bufHdr->usage_count++;
            }
            LocalRefCount[b]++;
            ResourceOwnerRememberBuffer(CurrentResourceOwner,
                                                      BufferDescriptorGetBuffer(bufHdr));
            if (bufHdr->flags & BM_VALID)
                  *foundPtr = TRUE;
            else
            {
                  /* Previous read attempt must have failed; try again */
                  *foundPtr = FALSE;
            }
            return bufHdr;
      }

#ifdef LBDEBUG
      fprintf(stderr, "LB ALLOC (%u,%d,%d) %d\n",
             smgr->smgr_rnode.relNode, forkNum, blockNum, -nextFreeLocalBuf - 1);
#endif

      /*
       * Need to get a new buffer.  We use a clock sweep algorithm (essentially
       * the same as what freelist.c does now...)
       */
      trycounter = NLocBuffer;
      for (;;)
      {
            b = nextFreeLocalBuf;

            if (++nextFreeLocalBuf >= NLocBuffer)
                  nextFreeLocalBuf = 0;

            bufHdr = &LocalBufferDescriptors[b];

            if (LocalRefCount[b] == 0)
            {
                  if (bufHdr->usage_count > 0)
                  {
                        bufHdr->usage_count--;
                        trycounter = NLocBuffer;
                  }
                  else
                  {
                        /* Found a usable buffer */
                        LocalRefCount[b]++;
                        ResourceOwnerRememberBuffer(CurrentResourceOwner,
                                                              BufferDescriptorGetBuffer(bufHdr));
                        break;
                  }
            }
            else if (--trycounter == 0)
                  ereport(ERROR,
                              (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
                               errmsg("no empty local buffer available")));
      }

      /*
       * this buffer is not referenced but it might still be dirty. if that's
       * the case, write it out before reusing it!
       */
      if (bufHdr->flags & BM_DIRTY)
      {
            SMgrRelation oreln;

            /* Find smgr relation for buffer */
            oreln = smgropen(bufHdr->tag.rnode);

            /* And write... */
            smgrwrite(oreln,
                          bufHdr->tag.forkNum,
                          bufHdr->tag.blockNum,
                          (char *) LocalBufHdrGetBlock(bufHdr),
                          true);

            /* Mark not-dirty now in case we error out below */
            bufHdr->flags &= ~BM_DIRTY;

            LocalBufferFlushCount++;
      }

      /*
       * lazy memory allocation: allocate space on first use of a buffer.
       */
      if (LocalBufHdrGetBlock(bufHdr) == NULL)
      {
            /* Set pointer for use by BufferGetBlock() macro */
            LocalBufHdrGetBlock(bufHdr) = GetLocalBufferStorage();
      }

      /*
       * Update the hash table: remove old entry, if any, and make new one.
       */
      if (bufHdr->flags & BM_TAG_VALID)
      {
            hresult = (LocalBufferLookupEnt *)
                  hash_search(LocalBufHash, (void *) &bufHdr->tag,
                                    HASH_REMOVE, NULL);
            if (!hresult)                 /* shouldn't happen */
                  elog(ERROR, "local buffer hash table corrupted");
            /* mark buffer invalid just in case hash insert fails */
            CLEAR_BUFFERTAG(bufHdr->tag);
            bufHdr->flags &= ~(BM_VALID | BM_TAG_VALID);
      }

      hresult = (LocalBufferLookupEnt *)
            hash_search(LocalBufHash, (void *) &newTag, HASH_ENTER, &found);
      if (found)                          /* shouldn't happen */
            elog(ERROR, "local buffer hash table corrupted");
      hresult->id = b;

      /*
       * it's all ours now.
       */
      bufHdr->tag = newTag;
      bufHdr->flags &= ~(BM_VALID | BM_DIRTY | BM_JUST_DIRTIED | BM_IO_ERROR);
      bufHdr->flags |= BM_TAG_VALID;
      bufHdr->usage_count = 1;

      *foundPtr = FALSE;
      return bufHdr;
}

/*
 * MarkLocalBufferDirty -
 *      mark a local buffer dirty
 */
void
MarkLocalBufferDirty(Buffer buffer)
{
      int               bufid;
      BufferDesc *bufHdr;

      Assert(BufferIsLocal(buffer));

#ifdef LBDEBUG
      fprintf(stderr, "LB DIRTY %d\n", buffer);
#endif

      bufid = -(buffer + 1);

      Assert(LocalRefCount[bufid] > 0);

      bufHdr = &LocalBufferDescriptors[bufid];
      bufHdr->flags |= BM_DIRTY;
}

/*
 * DropRelFileNodeLocalBuffers
 *          This function removes from the buffer pool all the pages of the
 *          specified relation that have block numbers >= firstDelBlock.
 *          (In particular, with firstDelBlock = 0, all pages are removed.)
 *          Dirty pages are simply dropped, without bothering to write them
 *          out first.  Therefore, this is NOT rollback-able, and so should be
 *          used only with extreme caution!
 *
 *          See DropRelFileNodeBuffers in bufmgr.c for more notes.
 */
void
DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum,
                                          BlockNumber firstDelBlock)
{
      int               i;

      for (i = 0; i < NLocBuffer; i++)
      {
            BufferDesc *bufHdr = &LocalBufferDescriptors[i];
            LocalBufferLookupEnt *hresult;

            if ((bufHdr->flags & BM_TAG_VALID) &&
                  RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
                  bufHdr->tag.forkNum == forkNum &&
                  bufHdr->tag.blockNum >= firstDelBlock)
            {
                  if (LocalRefCount[i] != 0)
                        elog(ERROR, "block %u of %s is still referenced (local %u)",
                               bufHdr->tag.blockNum,
                               relpath(bufHdr->tag.rnode, bufHdr->tag.forkNum),
                               LocalRefCount[i]);
                  /* Remove entry from hashtable */
                  hresult = (LocalBufferLookupEnt *)
                        hash_search(LocalBufHash, (void *) &bufHdr->tag,
                                          HASH_REMOVE, NULL);
                  if (!hresult)           /* shouldn't happen */
                        elog(ERROR, "local buffer hash table corrupted");
                  /* Mark buffer invalid */
                  CLEAR_BUFFERTAG(bufHdr->tag);
                  bufHdr->flags = 0;
                  bufHdr->usage_count = 0;
            }
      }
}

/*
 * InitLocalBuffers -
 *      init the local buffer cache. Since most queries (esp. multi-user ones)
 *      don't involve local buffers, we delay allocating actual memory for the
 *      buffers until we need them; just make the buffer headers here.
 */
static void
InitLocalBuffers(void)
{
      int               nbufs = num_temp_buffers;
      HASHCTL           info;
      int               i;

      /* Allocate and zero buffer headers and auxiliary arrays */
      LocalBufferDescriptors = (BufferDesc *) calloc(nbufs, sizeof(BufferDesc));
      LocalBufferBlockPointers = (Block *) calloc(nbufs, sizeof(Block));
      LocalRefCount = (int32 *) calloc(nbufs, sizeof(int32));
      if (!LocalBufferDescriptors || !LocalBufferBlockPointers || !LocalRefCount)
            ereport(FATAL,
                        (errcode(ERRCODE_OUT_OF_MEMORY),
                         errmsg("out of memory")));

      nextFreeLocalBuf = 0;

      /* initialize fields that need to start off nonzero */
      for (i = 0; i < nbufs; i++)
      {
            BufferDesc *buf = &LocalBufferDescriptors[i];

            /*
             * negative to indicate local buffer. This is tricky: shared buffers
             * start with 0. We have to start with -2. (Note that the routine
             * BufferDescriptorGetBuffer adds 1 to buf_id so our first buffer id
             * is -1.)
             */
            buf->buf_id = -i - 2;
      }

      /* Create the lookup hash table */
      MemSet(&info, 0, sizeof(info));
      info.keysize = sizeof(BufferTag);
      info.entrysize = sizeof(LocalBufferLookupEnt);
      info.hash = tag_hash;

      LocalBufHash = hash_create("Local Buffer Lookup Table",
                                             nbufs,
                                             &info,
                                             HASH_ELEM | HASH_FUNCTION);

      if (!LocalBufHash)
            elog(ERROR, "could not initialize local buffer hash table");

      /* Initialization done, mark buffers allocated */
      NLocBuffer = nbufs;
}

/*
 * GetLocalBufferStorage - allocate memory for a local buffer
 *
 * The idea of this function is to aggregate our requests for storage
 * so that the memory manager doesn't see a whole lot of relatively small
 * requests.  Since we'll never give back a local buffer once it's created
 * within a particular process, no point in burdening memmgr with separately
 * managed chunks.
 */
static Block
GetLocalBufferStorage(void)
{
      static char *cur_block = NULL;
      static int  next_buf_in_block = 0;
      static int  num_bufs_in_block = 0;
      static int  total_bufs_allocated = 0;

      char     *this_buf;

      Assert(total_bufs_allocated < NLocBuffer);

      if (next_buf_in_block >= num_bufs_in_block)
      {
            /* Need to make a new request to memmgr */
            int               num_bufs;

            /* Start with a 16-buffer request; subsequent ones double each time */
            num_bufs = Max(num_bufs_in_block * 2, 16);
            /* But not more than what we need for all remaining local bufs */
            num_bufs = Min(num_bufs, NLocBuffer - total_bufs_allocated);
            /* And don't overflow MaxAllocSize, either */
            num_bufs = Min(num_bufs, MaxAllocSize / BLCKSZ);

            /* Allocate space from TopMemoryContext so it never goes away */
            cur_block = (char *) MemoryContextAlloc(TopMemoryContext,
                                                                        num_bufs * BLCKSZ);
            next_buf_in_block = 0;
            num_bufs_in_block = num_bufs;
      }

      /* Allocate next buffer in current memory block */
      this_buf = cur_block + next_buf_in_block * BLCKSZ;
      next_buf_in_block++;
      total_bufs_allocated++;

      return (Block) this_buf;
}

/*
 * AtEOXact_LocalBuffers - clean up at end of transaction.
 *
 * This is just like AtEOXact_Buffers, but for local buffers.
 */
void
AtEOXact_LocalBuffers(bool isCommit)
{
#ifdef USE_ASSERT_CHECKING
      if (assert_enabled)
      {
            int               i;

            for (i = 0; i < NLocBuffer; i++)
            {
                  Assert(LocalRefCount[i] == 0);
            }
      }
#endif
}

/*
 * AtProcExit_LocalBuffers - ensure we have dropped pins during backend exit.
 *
 * This is just like AtProcExit_Buffers, but for local buffers.  We have
 * to drop pins to ensure that any attempt to drop temp files doesn't
 * fail in DropRelFileNodeBuffers.
 */
void
AtProcExit_LocalBuffers(void)
{
      /* just zero the refcounts ... */
      if (LocalRefCount)
            MemSet(LocalRefCount, 0, NLocBuffer * sizeof(*LocalRefCount));
}

Generated by  Doxygen 1.6.0   Back to index