1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-12 05:01:15 +03:00

Restructure local-buffer handling per recent pghackers discussion.

The local buffer manager is no longer used for newly-created relations
(unless they are TEMP); a new non-TEMP relation goes through the shared
bufmgr and thus will participate normally in checkpoints.  But TEMP relations
use the local buffer manager throughout their lifespan.  Also, operations
in TEMP relations are not logged in WAL, thus improving performance.
Since it's no longer necessary to fsync relations as they move out of the
local buffers into shared buffers, quite a lot of smgr.c/md.c/fd.c code
is no longer needed and has been removed: there's no concept of a dirty
relation anymore in md.c/fd.c, and we never fsync anything but WAL.
Still TODO: improve local buffer management algorithms so that it would
be reasonable to increase NLocBuffer.
This commit is contained in:
Tom Lane
2002-08-06 02:36:35 +00:00
parent 35cd432b18
commit 5df307c778
28 changed files with 543 additions and 955 deletions

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/buffer/buf_init.c,v 1.49 2002/06/20 20:29:34 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/storage/buffer/buf_init.c,v 1.50 2002/08/06 02:36:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -258,7 +258,7 @@ ShutdownBufferPoolAccess(void)
/* Release any buffer context locks we are holding */
UnlockBuffers();
/* Release any buffer reference counts we are holding */
ResetBufferPool(false);
AtEOXact_Buffers(false);
}
/* -----------------------------------------------------

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/buffer/bufmgr.c,v 1.127 2002/07/02 05:47:37 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/storage/buffer/bufmgr.c,v 1.128 2002/08/06 02:36:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -57,16 +57,9 @@
#include "pgstat.h"
#define BufferGetLSN(bufHdr) \
(*((XLogRecPtr*)MAKE_PTR((bufHdr)->data)))
(*((XLogRecPtr*) MAKE_PTR((bufHdr)->data)))
extern long int ReadBufferCount;
extern long int ReadLocalBufferCount;
extern long int BufferHitCount;
extern long int LocalBufferHitCount;
extern long int BufferFlushCount;
extern long int LocalBufferFlushCount;
static void WaitIO(BufferDesc *buf);
static void StartBufferIO(BufferDesc *buf, bool forInput);
static void TerminateBufferIO(BufferDesc *buf);
@@ -82,16 +75,12 @@ static Buffer ReadBufferInternal(Relation reln, BlockNumber blockNum,
bool bufferLockHeld);
static BufferDesc *BufferAlloc(Relation reln, BlockNumber blockNum,
bool *foundPtr);
static int ReleaseBufferWithBufferLock(Buffer buffer);
static int BufferReplace(BufferDesc *bufHdr);
#ifdef NOT_USED
void PrintBufferDescs(void);
#endif
static void write_buffer(Buffer buffer, bool unpin);
static void drop_relfilenode_buffers(RelFileNode rnode,
bool do_local, bool do_both);
static int release_buffer(Buffer buffer, bool havelock);
/*
* ReadBuffer -- returns a buffer containing the requested
@@ -140,7 +129,7 @@ ReadBufferInternal(Relation reln, BlockNumber blockNum,
bool isLocalBuf;
isExtend = (blockNum == P_NEW);
isLocalBuf = reln->rd_myxactonly;
isLocalBuf = reln->rd_istemp;
if (isLocalBuf)
{
@@ -684,10 +673,10 @@ ReleaseAndReadBuffer(Buffer buffer,
/*
* BufferSync -- Write all dirty buffers in the pool.
*
* This is called at checkpoint time and write out all dirty buffers.
* This is called at checkpoint time and writes out all dirty shared buffers.
*/
void
BufferSync()
BufferSync(void)
{
int i;
BufferDesc *bufHdr;
@@ -780,8 +769,7 @@ BufferSync()
status = smgrblindwrt(DEFAULT_SMGR,
bufHdr->tag.rnode,
bufHdr->tag.blockNum,
(char *) MAKE_PTR(bufHdr->data),
true); /* must fsync */
(char *) MAKE_PTR(bufHdr->data));
}
else
{
@@ -908,19 +896,16 @@ ResetBufferUsage(void)
NDirectFileWrite = 0;
}
/* ----------------------------------------------
* ResetBufferPool
/*
* AtEOXact_Buffers - clean up at end of transaction.
*
* This routine is supposed to be called when a transaction aborts.
* It will release all the buffer pins held by the transaction.
* Currently, we also call it during commit if BufferPoolCheckLeak
* detected a problem --- in that case, isCommit is TRUE, and we
* only clean up buffer pin counts.
*
* ----------------------------------------------
* During abort, we need to release any buffer pins we're holding
* (this cleans up in case elog interrupted a routine that pins a
* buffer). During commit, we shouldn't need to do that, but check
* anyway to see if anyone leaked a buffer reference count.
*/
void
ResetBufferPool(bool isCommit)
AtEOXact_Buffers(bool isCommit)
{
int i;
@@ -928,7 +913,16 @@ ResetBufferPool(bool isCommit)
{
if (PrivateRefCount[i] != 0)
{
BufferDesc *buf = &BufferDescriptors[i];
BufferDesc *buf = &(BufferDescriptors[i]);
if (isCommit)
elog(WARNING,
"Buffer Leak: [%03d] (freeNext=%d, freePrev=%d, "
"rel=%u/%u, blockNum=%u, flags=0x%x, refcount=%d %ld)",
i, buf->freeNext, buf->freePrev,
buf->tag.rnode.tblNode, buf->tag.rnode.relNode,
buf->tag.blockNum, buf->flags,
buf->refcount, PrivateRefCount[i]);
PrivateRefCount[i] = 1; /* make sure we release shared pin */
LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
@@ -938,48 +932,15 @@ ResetBufferPool(bool isCommit)
}
}
ResetLocalBufferPool();
if (!isCommit)
smgrabort();
AtEOXact_LocalBuffers(isCommit);
}
/*
* BufferPoolCheckLeak
*
* check if there is buffer leak
*/
bool
BufferPoolCheckLeak(void)
{
int i;
bool result = false;
for (i = 0; i < NBuffers; i++)
{
if (PrivateRefCount[i] != 0)
{
BufferDesc *buf = &(BufferDescriptors[i]);
elog(WARNING,
"Buffer Leak: [%03d] (freeNext=%d, freePrev=%d, \
rel=%u/%u, blockNum=%u, flags=0x%x, refcount=%d %ld)",
i, buf->freeNext, buf->freePrev,
buf->tag.rnode.tblNode, buf->tag.rnode.relNode,
buf->tag.blockNum, buf->flags,
buf->refcount, PrivateRefCount[i]);
result = true;
}
}
return result;
}
/* ------------------------------------------------
* FlushBufferPool
*
* Flush all dirty blocks in buffer pool to disk
* at the checkpoint time
* ------------------------------------------------
* Flush all dirty blocks in buffer pool to disk at the checkpoint time.
* Local relations do not participate in checkpoints, so they don't need to be
* flushed.
*/
void
FlushBufferPool(void)
@@ -989,16 +950,13 @@ FlushBufferPool(void)
}
/*
* At the commit time we have to flush local buffer pool only
* Do whatever is needed to prepare for commit at the bufmgr and smgr levels
*/
void
BufmgrCommit(void)
{
LocalBufferSync();
/* Nothing to do in bufmgr anymore... */
/*
* All files created in current transaction will be fsync-ed
*/
smgrcommit();
}
@@ -1051,15 +1009,15 @@ BufferReplace(BufferDesc *bufHdr)
if (reln != (Relation) NULL)
{
status = smgrwrite(DEFAULT_SMGR, reln, bufHdr->tag.blockNum,
status = smgrwrite(DEFAULT_SMGR, reln,
bufHdr->tag.blockNum,
(char *) MAKE_PTR(bufHdr->data));
}
else
{
status = smgrblindwrt(DEFAULT_SMGR, bufHdr->tag.rnode,
bufHdr->tag.blockNum,
(char *) MAKE_PTR(bufHdr->data),
false); /* no fsync */
(char *) MAKE_PTR(bufHdr->data));
}
/* drop relcache refcnt incremented by RelationNodeCacheGetRelation */
@@ -1091,31 +1049,55 @@ RelationGetNumberOfBlocks(Relation relation)
{
/*
* relation->rd_nblocks should be accurate already if the relation is
* myxactonly. (XXX how safe is that really?) Don't call smgr on a
* view, either.
* new or temp, because no one else should be modifying it. Otherwise
* we need to ask the smgr for the current physical file length.
*
* Don't call smgr on a view, either.
*/
if (relation->rd_rel->relkind == RELKIND_VIEW)
relation->rd_nblocks = 0;
else if (!relation->rd_myxactonly)
else if (!relation->rd_isnew && !relation->rd_istemp)
relation->rd_nblocks = smgrnblocks(DEFAULT_SMGR, relation);
return relation->rd_nblocks;
}
/*
* drop_relfilenode_buffers -- common functionality for
* DropRelationBuffers and
* DropRelFileNodeBuffers
/* ---------------------------------------------------------------------
* DropRelationBuffers
*
* XXX currently it sequentially searches the buffer pool, should be
* changed to more clever ways of searching.
* This function removes all the buffered pages for a relation
* from the buffer pool. Dirty pages are simply dropped, without
* bothering to write them out first. This is NOT rollback-able,
* and so should be used only with extreme caution!
*
* We assume that the caller holds an exclusive lock on the relation,
* which should assure that no new buffers will be acquired for the rel
* meanwhile.
* --------------------------------------------------------------------
*/
static void
drop_relfilenode_buffers(RelFileNode rnode, bool do_local, bool do_both)
void
DropRelationBuffers(Relation rel)
{
DropRelFileNodeBuffers(rel->rd_node, rel->rd_istemp);
}
/* ---------------------------------------------------------------------
* DropRelFileNodeBuffers
*
* This is the same as DropRelationBuffers, except that the target
* relation is specified by RelFileNode and temp status.
*
* This is NOT rollback-able. One legitimate use is to clear the
* buffer cache of buffers for a relation that is being deleted
* during transaction abort.
* --------------------------------------------------------------------
*/
void
DropRelFileNodeBuffers(RelFileNode rnode, bool istemp)
{
int i;
BufferDesc *bufHdr;
if (do_local)
if (istemp)
{
for (i = 0; i < NLocBuffer; i++)
{
@@ -1128,8 +1110,7 @@ drop_relfilenode_buffers(RelFileNode rnode, bool do_local, bool do_both)
bufHdr->tag.rnode.relNode = InvalidOid;
}
}
if (!do_both)
return;
return;
}
LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
@@ -1160,18 +1141,19 @@ recheck:
bufHdr->cntxDirty = false;
/*
* Release any refcount we may have.
*
* This is very probably dead code, and if it isn't then it's
* probably wrong. I added the Assert to find out --- tgl
* 11/99.
* Release any refcount we may have. If someone else has a
* pin on the buffer, we got trouble.
*/
if (!(bufHdr->flags & BM_FREE))
{
/* Assert checks that buffer will actually get freed! */
Assert(PrivateRefCount[i - 1] == 1 &&
bufHdr->refcount == 1);
ReleaseBufferWithBufferLock(i);
/* the sole pin should be ours */
if (bufHdr->refcount != 1 || PrivateRefCount[i - 1] == 0)
elog(FATAL, "DropRelFileNodeBuffers: block %u is referenced (private %ld, global %d)",
bufHdr->tag.blockNum,
PrivateRefCount[i - 1], bufHdr->refcount);
/* Make sure it will be released */
PrivateRefCount[i - 1] = 1;
UnpinBuffer(bufHdr);
}
/*
@@ -1184,43 +1166,6 @@ recheck:
LWLockRelease(BufMgrLock);
}
/* ---------------------------------------------------------------------
* DropRelationBuffers
*
* This function removes all the buffered pages for a relation
* from the buffer pool. Dirty pages are simply dropped, without
* bothering to write them out first. This is NOT rollback-able,
* and so should be used only with extreme caution!
*
* We assume that the caller holds an exclusive lock on the relation,
* which should assure that no new buffers will be acquired for the rel
* meanwhile.
* --------------------------------------------------------------------
*/
void
DropRelationBuffers(Relation rel)
{
drop_relfilenode_buffers(rel->rd_node, rel->rd_myxactonly, false);
}
/* ---------------------------------------------------------------------
* DropRelFileNodeBuffers
*
* This is the same as DropRelationBuffers, except that the target
* relation is specified by RelFileNode.
*
* This is NOT rollback-able. One legitimate use is to clear the
* buffer cache of buffers for a relation that is being deleted
* during transaction abort.
* --------------------------------------------------------------------
*/
void
DropRelFileNodeBuffers(RelFileNode rnode)
{
/* We have to search both local and shared buffers... */
drop_relfilenode_buffers(rnode, true, true);
}
/* ---------------------------------------------------------------------
* DropBuffers
*
@@ -1296,7 +1241,7 @@ recheck:
*/
#ifdef NOT_USED
void
PrintBufferDescs()
PrintBufferDescs(void)
{
int i;
BufferDesc *buf = BufferDescriptors;
@@ -1331,7 +1276,7 @@ blockNum=%u, flags=0x%x, refcount=%d %ld)",
#ifdef NOT_USED
void
PrintPinnedBufs()
PrintPinnedBufs(void)
{
int i;
BufferDesc *buf = BufferDescriptors;
@@ -1351,33 +1296,6 @@ blockNum=%u, flags=0x%x, refcount=%d %ld)",
}
#endif
/*
* BufferPoolBlowaway
*
* this routine is solely for the purpose of experiments -- sometimes
* you may want to blowaway whatever is left from the past in buffer
* pool and start measuring some performance with a clean empty buffer
* pool.
*/
#ifdef NOT_USED
void
BufferPoolBlowaway()
{
int i;
BufferSync();
for (i = 1; i <= NBuffers; i++)
{
if (BufferIsValid(i))
{
while (BufferIsValid(i))
ReleaseBuffer(i);
}
BufTableDelete(&BufferDescriptors[i - 1]);
}
}
#endif
/* ---------------------------------------------------------------------
* FlushRelationBuffers
*
@@ -1428,7 +1346,7 @@ FlushRelationBuffers(Relation rel, BlockNumber firstDelBlock)
XLogRecPtr recptr;
int status;
if (rel->rd_myxactonly)
if (rel->rd_istemp)
{
for (i = 0; i < NLocBuffer; i++)
{
@@ -1544,12 +1462,14 @@ FlushRelationBuffers(Relation rel, BlockNumber firstDelBlock)
return 0;
}
#undef ReleaseBuffer
/*
* release_buffer -- common functionality for
* ReleaseBuffer and ReleaseBufferWithBufferLock
* ReleaseBuffer -- remove the pin on a buffer without
* marking it dirty.
*/
static int
release_buffer(Buffer buffer, bool havelock)
int
ReleaseBuffer(Buffer buffer)
{
BufferDesc *bufHdr;
@@ -1570,41 +1490,14 @@ release_buffer(Buffer buffer, bool havelock)
PrivateRefCount[buffer - 1]--;
else
{
if (!havelock)
LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
UnpinBuffer(bufHdr);
if (!havelock)
LWLockRelease(BufMgrLock);
LWLockRelease(BufMgrLock);
}
return STATUS_OK;
}
#undef ReleaseBuffer
/*
* ReleaseBuffer -- remove the pin on a buffer without
* marking it dirty.
*/
int
ReleaseBuffer(Buffer buffer)
{
return release_buffer(buffer, false);
}
/*
* ReleaseBufferWithBufferLock
* Same as ReleaseBuffer except we hold the bufmgr lock
*/
static int
ReleaseBufferWithBufferLock(Buffer buffer)
{
return release_buffer(buffer, true);
}
#ifdef NOT_USED
void
IncrBufferRefCount_Debug(char *file, int line, Buffer buffer)
@@ -1847,10 +1740,13 @@ SetBufferCommitInfoNeedsSave(Buffer buffer)
BufferDesc *bufHdr;
if (BufferIsLocal(buffer))
{
WriteLocalBuffer(buffer, false);
return;
}
if (BAD_BUFFER_ID(buffer))
return;
elog(ERROR, "SetBufferCommitInfoNeedsSave: bad buffer %d", buffer);
bufHdr = &BufferDescriptors[buffer - 1];

View File

@@ -1,48 +1,37 @@
/*-------------------------------------------------------------------------
*
* localbuf.c
* local buffer manager. Fast buffer manager for temporary tables
* or special cases when the operation is not visible to other backends.
*
* When a relation is being created, the descriptor will have rd_islocal
* set to indicate that the local buffer manager should be used. During
* the same transaction the relation is being created, any inserts or
* selects from the newly created relation will use the local buffer
* pool. rd_islocal is reset at the end of a transaction (commit/abort).
* This is useful for queries like SELECT INTO TABLE and create index.
* local buffer manager. Fast buffer manager for temporary tables,
* which never need to be WAL-logged or checkpointed, etc.
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/buffer/localbuf.c,v 1.44 2002/06/20 20:29:34 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/storage/buffer/localbuf.c,v 1.45 2002/08/06 02:36:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/types.h>
#include <sys/file.h>
#include <math.h>
#include <signal.h>
#include "executor/execdebug.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/smgr.h"
#include "utils/relcache.h"
extern long int LocalBufferFlushCount;
/*#define LBDEBUG*/
/* should be a GUC parameter some day */
int NLocBuffer = 64;
BufferDesc *LocalBufferDescriptors = NULL;
Block *LocalBufferBlockPointers = NULL;
long *LocalRefCount = NULL;
static int nextFreeLocalBuf = 0;
/*#define LBDEBUG*/
/*
* LocalBufferAlloc -
@@ -61,11 +50,11 @@ LocalBufferAlloc(Relation reln, BlockNumber blockNum, bool *foundPtr)
reln->rd_node.relNode &&
LocalBufferDescriptors[i].tag.blockNum == blockNum)
{
#ifdef LBDEBUG
fprintf(stderr, "LB ALLOC (%u,%d) %d\n",
RelationGetRelid(reln), blockNum, -i - 1);
#endif
LocalRefCount[i]++;
*foundPtr = TRUE;
return &LocalBufferDescriptors[i];
@@ -94,14 +83,17 @@ LocalBufferAlloc(Relation reln, BlockNumber blockNum, bool *foundPtr)
elog(ERROR, "no empty local buffer.");
/*
* this buffer is not referenced but it might still be dirty (the last
* transaction to touch it doesn't need its contents but has not
* flushed it). if that's the case, write it out before reusing it!
* 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 || bufHdr->cntxDirty)
{
Relation bufrel = RelationNodeCacheGetRelation(bufHdr->tag.rnode);
/*
* The relcache is not supposed to throw away temp rels, so this
* should always succeed.
*/
Assert(bufrel != NULL);
/* flush this page */
@@ -113,26 +105,19 @@ LocalBufferAlloc(Relation reln, BlockNumber blockNum, bool *foundPtr)
RelationDecrementReferenceCount(bufrel);
}
/*
* it's all ours now.
*
* We need not in tblNode currently but will in future I think, when
* we'll give up rel->rd_fd to fmgr cache.
*/
bufHdr->tag.rnode = reln->rd_node;
bufHdr->tag.blockNum = blockNum;
bufHdr->flags &= ~BM_DIRTY;
bufHdr->cntxDirty = false;
/*
* lazy memory allocation: allocate space on first use of a buffer.
*
* Note this path cannot be taken for a buffer that was previously
* in use, so it's okay to do it (and possibly error out) before
* marking the buffer as valid.
*/
if (bufHdr->data == (SHMEM_OFFSET) 0)
{
char *data = (char *) malloc(BLCKSZ);
if (data == NULL)
elog(FATAL, "Out of memory in LocalBufferAlloc");
elog(ERROR, "Out of memory in LocalBufferAlloc");
/*
* This is a bit of a hack: bufHdr->data needs to be a shmem
@@ -147,13 +132,24 @@ LocalBufferAlloc(Relation reln, BlockNumber blockNum, bool *foundPtr)
LocalBufferBlockPointers[-(bufHdr->buf_id + 2)] = (Block) data;
}
/*
* it's all ours now.
*
* We need not in tblNode currently but will in future I think, when
* we'll give up rel->rd_fd to fmgr cache.
*/
bufHdr->tag.rnode = reln->rd_node;
bufHdr->tag.blockNum = blockNum;
bufHdr->flags &= ~BM_DIRTY;
bufHdr->cntxDirty = false;
*foundPtr = FALSE;
return bufHdr;
}
/*
* WriteLocalBuffer -
* writes out a local buffer
* writes out a local buffer (actually, just marks it dirty)
*/
void
WriteLocalBuffer(Buffer buffer, bool release)
@@ -180,7 +176,7 @@ WriteLocalBuffer(Buffer buffer, bool release)
* InitLocalBuffer -
* init the local buffer cache. Since most queries (esp. multi-user ones)
* don't involve local buffers, we delay allocating actual memory for the
* buffer until we need it.
* buffers until we need them; just make the buffer headers here.
*/
void
InitLocalBuffer(void)
@@ -211,65 +207,30 @@ InitLocalBuffer(void)
}
/*
* LocalBufferSync
* AtEOXact_LocalBuffers - clean up at end of transaction.
*
* Flush all dirty buffers in the local buffer cache at commit time.
* Since the buffer cache is only used for keeping relations visible
* during a transaction, we will not need these buffers again.
*
* Note that we have to *flush* local buffers because of them are not
* visible to checkpoint makers. But we can skip XLOG flush check.
* This is just like AtEOXact_Buffers, but for local buffers.
*/
void
LocalBufferSync(void)
AtEOXact_LocalBuffers(bool isCommit)
{
int i;
for (i = 0; i < NLocBuffer; i++)
{
BufferDesc *buf = &LocalBufferDescriptors[i];
Relation bufrel;
if (buf->flags & BM_DIRTY || buf->cntxDirty)
if (LocalRefCount[i] != 0)
{
#ifdef LBDEBUG
fprintf(stderr, "LB SYNC %d\n", -i - 1);
#endif
bufrel = RelationNodeCacheGetRelation(buf->tag.rnode);
BufferDesc *buf = &(LocalBufferDescriptors[i]);
Assert(bufrel != NULL);
if (isCommit)
elog(WARNING,
"Local Buffer Leak: [%03d] (rel=%u/%u, blockNum=%u, flags=0x%x, refcount=%d %ld)",
i,
buf->tag.rnode.tblNode, buf->tag.rnode.relNode,
buf->tag.blockNum, buf->flags,
buf->refcount, LocalRefCount[i]);
smgrwrite(DEFAULT_SMGR, bufrel, buf->tag.blockNum,
(char *) MAKE_PTR(buf->data));
smgrmarkdirty(DEFAULT_SMGR, bufrel, buf->tag.blockNum);
LocalBufferFlushCount++;
/* drop relcache refcount from RelationNodeCacheGetRelation */
RelationDecrementReferenceCount(bufrel);
buf->flags &= ~BM_DIRTY;
buf->cntxDirty = false;
LocalRefCount[i] = 0;
}
}
MemSet(LocalRefCount, 0, sizeof(long) * NLocBuffer);
nextFreeLocalBuf = 0;
}
void
ResetLocalBufferPool(void)
{
int i;
for (i = 0; i < NLocBuffer; i++)
{
BufferDesc *buf = &LocalBufferDescriptors[i];
buf->tag.rnode.relNode = InvalidOid;
buf->flags &= ~BM_DIRTY;
buf->cntxDirty = false;
}
MemSet(LocalRefCount, 0, sizeof(long) * NLocBuffer);
nextFreeLocalBuf = 0;
}