git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@204 0109f412-320b-0410-ab79-c3e0c5ffbbe6
3064 lines
82 KiB
C++
3064 lines
82 KiB
C++
//-------------------------------------------------------------------------
|
|
// Desc: Record caching
|
|
// Tabs: 3
|
|
//
|
|
// Copyright (c) 1999-2006 Novell, Inc. All Rights Reserved.
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of version 2 of the GNU General Public
|
|
// License as published by the Free Software Foundation.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, contact Novell, Inc.
|
|
//
|
|
// To contact Novell about this file by physical or electronic mail,
|
|
// you may find current contact information at www.novell.com
|
|
//
|
|
// $Id: rcache.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $
|
|
//-------------------------------------------------------------------------
|
|
|
|
#include "flaimsys.h"
|
|
|
|
#if defined( FLM_NLM) && !defined( __MWERKS__)
|
|
// Disable "Warning! W549: col(XX) 'sizeof' operand contains
|
|
// compiler generated information"
|
|
#pragma warning 549 9
|
|
#endif
|
|
|
|
FSTATIC FLMBOOL rcaCanRelocate(
|
|
void * pvAlloc);
|
|
|
|
FSTATIC void rcaRelocate(
|
|
void * pvOldAlloc,
|
|
void * pvNewAlloc);
|
|
|
|
// Extended record object for accessing private members of FlmRecord
|
|
|
|
struct FlmRecordExt
|
|
{
|
|
static FINLINE void clearCached(
|
|
FlmRecord * pRec)
|
|
{
|
|
pRec->clearCached();
|
|
}
|
|
|
|
static FINLINE void setCached(
|
|
FlmRecord * pRec)
|
|
{
|
|
pRec->setCached();
|
|
}
|
|
|
|
static FINLINE void setReadOnly(
|
|
FlmRecord * pRec)
|
|
{
|
|
pRec->setReadOnly();
|
|
}
|
|
|
|
static FINLINE FLMINT AddRef(
|
|
FlmRecord * pRec,
|
|
FLMBOOL bMutexLocked)
|
|
{
|
|
return( pRec->AddRef( bMutexLocked));
|
|
}
|
|
|
|
static FINLINE FLMINT Release(
|
|
FlmRecord * pRec,
|
|
FLMBOOL bMutexLocked)
|
|
{
|
|
return( pRec->Release( bMutexLocked));
|
|
}
|
|
|
|
static FINLINE void setOldVersion(
|
|
FlmRecord * pRec)
|
|
{
|
|
pRec->setOldVersion();
|
|
}
|
|
|
|
static FINLINE void clearOldVersion(
|
|
FlmRecord * pRec)
|
|
{
|
|
pRec->clearOldVersion();
|
|
}
|
|
|
|
static FINLINE FLMUINT getFlags(
|
|
FlmRecord * pRec)
|
|
{
|
|
return( pRec->m_uiFlags);
|
|
}
|
|
|
|
static FLMBOOL canRelocateRec(
|
|
void * pvAlloc);
|
|
|
|
static void relocateRec(
|
|
void * pvOldAlloc,
|
|
void * pvNewAlloc);
|
|
|
|
static FLMBOOL canRelocateRecBuffer(
|
|
void * pvAlloc);
|
|
|
|
static void relocateRecBuffer(
|
|
void * pvOldAlloc,
|
|
void * pvNewAlloc);
|
|
};
|
|
|
|
// Functions for calculating minimum and maximum record counts for a
|
|
// given hash table size.
|
|
|
|
#define FLM_RCA_MIN_REC_CNT(uiHashTblSz) ((uiHashTblSz) / 4)
|
|
#define FLM_RCA_MAX_REC_CNT(uiHashTblSz) ((uiHashTblSz) * 4)
|
|
|
|
// Hash function for hashing to records in record cache.
|
|
|
|
#define FLM_RCA_HASH( uiDrn) \
|
|
(RCACHE **)(&(gv_FlmSysData.RCacheMgr.ppHashBuckets[(uiDrn) & \
|
|
(gv_FlmSysData.RCacheMgr.uiHashMask)]))
|
|
|
|
/* LOCAL STATIC FUNCTION PROTOTYPES */
|
|
|
|
FSTATIC void flmRcaFreePurged(
|
|
RCACHE * pRCache);
|
|
|
|
FSTATIC void flmRcaFreeCache(
|
|
RCACHE * pRCache,
|
|
FLMBOOL bPutInPurgeList);
|
|
|
|
FSTATIC FLMUINT flmRcaGetBestHashTblSize(
|
|
FLMUINT uiCurrRecCount);
|
|
|
|
FSTATIC RCODE flmRcaRehash( void);
|
|
|
|
FSTATIC RCODE flmRcaSetMemLimit(
|
|
FLMUINT uiMaxCacheBytes);
|
|
|
|
FSTATIC RCODE flmRcaWaitNotify(
|
|
FNOTIFY ** ppNotifyListRV);
|
|
|
|
FSTATIC void flmRcaNotify(
|
|
FNOTIFY * pNotify,
|
|
RCACHE * pUseRCache,
|
|
RCODE NotifyRc);
|
|
|
|
FSTATIC RCODE flmRcaAllocCacheStruct(
|
|
RCACHE ** ppRCache);
|
|
|
|
FSTATIC void flmRcaFreeCacheStruct(
|
|
RCACHE ** ppRCache);
|
|
|
|
FSTATIC void flmRcaSetRecord(
|
|
RCACHE * pRCache,
|
|
FlmRecord * pNewRecord);
|
|
|
|
FSTATIC void flmRcaLinkIntoRCache(
|
|
RCACHE * pNewerRCache,
|
|
RCACHE * pOlderRCache,
|
|
RCACHE * pRCache,
|
|
FLMBOOL bLinkAsMRU);
|
|
|
|
FSTATIC void flmRcaLinkToFFILE(
|
|
RCACHE * pRCache,
|
|
FFILE_p pFile,
|
|
FDB_p pDb,
|
|
FLMUINT uiLowTransId,
|
|
FLMBOOL bMostCurrent);
|
|
|
|
#ifdef FLM_DEBUG
|
|
FSTATIC RCODE flmRcaCheck(
|
|
FDB_p pDb,
|
|
FLMUINT uiContainer,
|
|
FLMUINT uiDrn);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
Desc: This inline assumes that the global mutex is locked, because
|
|
it potentially updates the cache usage statistics.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaSetTransID(
|
|
RCACHE * pRCache,
|
|
FLMUINT uiNewTransID)
|
|
{
|
|
if (pRCache->uiHighTransId == 0xFFFFFFFF &&
|
|
uiNewTransID != 0xFFFFFFFF)
|
|
{
|
|
FLMUINT uiSize = (FLMUINT)((pRCache->pRecord)
|
|
? pRCache->pRecord->getTotalMemory()
|
|
: (FLMUINT)0) + sizeof( RCACHE);
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes += uiSize;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount++;
|
|
|
|
if( pRCache->pRecord)
|
|
{
|
|
FlmRecordExt::setOldVersion( pRCache->pRecord);
|
|
}
|
|
}
|
|
else if (pRCache->uiHighTransId != 0xFFFFFFFF &&
|
|
uiNewTransID == 0xFFFFFFFF)
|
|
{
|
|
FLMUINT uiSize = (FLMUINT)((pRCache->pRecord)
|
|
? pRCache->pRecord->getTotalMemory()
|
|
: (FLMUINT)0) + sizeof( RCACHE);
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes >= uiSize);
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount);
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes -= uiSize;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount--;
|
|
if( pRCache->pRecord)
|
|
{
|
|
FlmRecordExt::clearOldVersion( pRCache->pRecord);
|
|
}
|
|
}
|
|
pRCache->uiHighTransId = uiNewTransID;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a record into the global list as the MRU record.
|
|
This routine assumes that the record cache mutex has already
|
|
been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaLinkToGlobalAsMRU(
|
|
RCACHE * pRCache)
|
|
{
|
|
pRCache->pPrevInGlobal = NULL;
|
|
if ((pRCache->pNextInGlobal = gv_FlmSysData.RCacheMgr.pMRURecord) != NULL)
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pMRURecord->pPrevInGlobal = pRCache;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pLRURecord = pRCache;
|
|
}
|
|
gv_FlmSysData.RCacheMgr.pMRURecord = pRCache;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a record into the global list as the LRU record.
|
|
This routine assumes that the record cache mutex has already
|
|
been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaLinkToGlobalAsLRU(
|
|
RCACHE * pRCache)
|
|
{
|
|
pRCache->pNextInGlobal = NULL;
|
|
if ((pRCache->pPrevInGlobal = gv_FlmSysData.RCacheMgr.pLRURecord) != NULL)
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pLRURecord->pNextInGlobal = pRCache;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pMRURecord = pRCache;
|
|
}
|
|
gv_FlmSysData.RCacheMgr.pLRURecord = pRCache;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Moves a record one step closer to the MRU slot in the global list.
|
|
This routine assumes that the record cache mutex has already
|
|
been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaStepUpInGlobalList(
|
|
RCACHE * pRCache)
|
|
{
|
|
RCACHE * pPrevRCache;
|
|
|
|
if( (pPrevRCache = pRCache->pPrevInGlobal) != NULL)
|
|
{
|
|
if( pPrevRCache->pPrevInGlobal)
|
|
{
|
|
pPrevRCache->pPrevInGlobal->pNextInGlobal = pRCache;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pMRURecord = pRCache;
|
|
}
|
|
|
|
pRCache->pPrevInGlobal = pPrevRCache->pPrevInGlobal;
|
|
pPrevRCache->pPrevInGlobal = pRCache;
|
|
pPrevRCache->pNextInGlobal = pRCache->pNextInGlobal;
|
|
|
|
if( pRCache->pNextInGlobal)
|
|
{
|
|
pRCache->pNextInGlobal->pPrevInGlobal = pPrevRCache;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pLRURecord = pPrevRCache;
|
|
}
|
|
pRCache->pNextInGlobal = pPrevRCache;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine unlinks a record from the global list This routine
|
|
assumes that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaUnlinkFromGlobal(
|
|
RCACHE * pRCache)
|
|
{
|
|
if (pRCache->pNextInGlobal)
|
|
{
|
|
pRCache->pNextInGlobal->pPrevInGlobal = pRCache->pPrevInGlobal;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pLRURecord = pRCache->pPrevInGlobal;
|
|
}
|
|
if (pRCache->pPrevInGlobal)
|
|
{
|
|
pRCache->pPrevInGlobal->pNextInGlobal = pRCache->pNextInGlobal;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pMRURecord = pRCache->pNextInGlobal;
|
|
}
|
|
pRCache->pPrevInGlobal = pRCache->pNextInGlobal = NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine unlinks a record from the global purged list This routine
|
|
assumes that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaUnlinkFromPurged(
|
|
RCACHE * pRCache)
|
|
{
|
|
if (pRCache->pNextInGlobal)
|
|
{
|
|
pRCache->pNextInGlobal->pPrevInGlobal = pRCache->pPrevInGlobal;
|
|
}
|
|
|
|
if (pRCache->pPrevInGlobal)
|
|
{
|
|
pRCache->pPrevInGlobal->pNextInGlobal = pRCache->pNextInGlobal;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pPurgeList = pRCache->pNextInGlobal;
|
|
}
|
|
|
|
pRCache->pPrevInGlobal = NULL;
|
|
pRCache->pNextInGlobal = NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
****************************************************************************/
|
|
FINLINE void flmRcaLinkToHeapList(
|
|
RCACHE * pRCache)
|
|
{
|
|
flmAssert( !pRCache->pPrevInHeapList);
|
|
flmAssert( !pRCache->pNextInHeapList);
|
|
flmAssert( !RCA_IS_IN_HEAP_LIST( pRCache->uiFlags));
|
|
flmAssert( FlmRecordExt::getFlags( pRCache->pRecord) & RCA_HEAP_BUFFER);
|
|
|
|
if( (pRCache->pNextInHeapList = gv_FlmSysData.RCacheMgr.pHeapList) != NULL)
|
|
{
|
|
pRCache->pNextInHeapList->pPrevInHeapList = pRCache;
|
|
}
|
|
gv_FlmSysData.RCacheMgr.pHeapList = pRCache;
|
|
RCA_SET_IN_HEAP_LIST( pRCache->uiFlags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
****************************************************************************/
|
|
FINLINE void flmRcaUnlinkFromHeapList(
|
|
RCACHE * pRCache)
|
|
{
|
|
flmAssert( RCA_IS_IN_HEAP_LIST( pRCache->uiFlags));
|
|
|
|
if( pRCache->pNextInHeapList)
|
|
{
|
|
pRCache->pNextInHeapList->pPrevInHeapList = pRCache->pPrevInHeapList;
|
|
}
|
|
|
|
if( pRCache->pPrevInHeapList)
|
|
{
|
|
pRCache->pPrevInHeapList->pNextInHeapList = pRCache->pNextInHeapList;
|
|
}
|
|
else
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pHeapList = pRCache->pNextInHeapList;
|
|
}
|
|
|
|
pRCache->pPrevInHeapList = NULL;
|
|
pRCache->pNextInHeapList = NULL;
|
|
|
|
RCA_UNSET_IN_HEAP_LIST( pRCache->uiFlags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a record to an FFILE list at the head of the list.
|
|
This routine assumes that the record cache mutex has already been
|
|
locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaLinkToFileAtHead(
|
|
RCACHE * pRCache,
|
|
FFILE_p pFile)
|
|
{
|
|
pRCache->pPrevInFile = NULL;
|
|
if ((pRCache->pNextInFile = pFile->pFirstRecord) != NULL)
|
|
{
|
|
pFile->pFirstRecord->pPrevInFile = pRCache;
|
|
}
|
|
else
|
|
{
|
|
pFile->pLastRecord = pRCache;
|
|
}
|
|
|
|
pFile->pFirstRecord = pRCache;
|
|
pRCache->pFile = pFile;
|
|
RCA_SET_LINKED_TO_FILE( pRCache->uiFlags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a record to an FFILE list at the end of the list.
|
|
This routine assumes that the record cache mutex has already been
|
|
locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaLinkToFileAtEnd(
|
|
RCACHE * pRCache,
|
|
FFILE_p pFile)
|
|
{
|
|
pRCache->pNextInFile = NULL;
|
|
if( (pRCache->pPrevInFile = pFile->pLastRecord) != NULL)
|
|
{
|
|
pFile->pLastRecord->pNextInFile = pRCache;
|
|
}
|
|
else
|
|
{
|
|
pFile->pFirstRecord = pRCache;
|
|
}
|
|
pFile->pLastRecord = pRCache;
|
|
pRCache->pFile = pFile;
|
|
RCA_SET_LINKED_TO_FILE( pRCache->uiFlags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine unlinks a record from its FFILE list. This routine
|
|
assumes that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaUnlinkFromFile(
|
|
RCACHE * pRCache)
|
|
{
|
|
if( RCA_IS_LINKED_TO_FILE( pRCache->uiFlags))
|
|
{
|
|
if( pRCache->pNextInFile)
|
|
{
|
|
pRCache->pNextInFile->pPrevInFile = pRCache->pPrevInFile;
|
|
}
|
|
else
|
|
{
|
|
pRCache->pFile->pLastRecord = pRCache->pPrevInFile;
|
|
}
|
|
if( pRCache->pPrevInFile)
|
|
{
|
|
pRCache->pPrevInFile->pNextInFile = pRCache->pNextInFile;
|
|
}
|
|
else
|
|
{
|
|
pRCache->pFile->pFirstRecord = pRCache->pNextInFile;
|
|
}
|
|
pRCache->pPrevInFile = pRCache->pNextInFile = NULL;
|
|
RCA_UNSET_LINKED_TO_FILE( pRCache->uiFlags);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a record into its hash bucket. This routine
|
|
assumes that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaLinkToHashBucket(
|
|
RCACHE * pRCache)
|
|
{
|
|
RCACHE ** ppHashBucket = FLM_RCA_HASH( pRCache->uiDrn);
|
|
|
|
flmAssert( pRCache->pNewerVersion == NULL);
|
|
|
|
pRCache->pPrevInBucket = NULL;
|
|
if( (pRCache->pNextInBucket = *ppHashBucket) != NULL)
|
|
{
|
|
pRCache->pNextInBucket->pPrevInBucket = pRCache;
|
|
}
|
|
*ppHashBucket = pRCache;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine unlinks a record from its hash bucket. This routine
|
|
assumes that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaUnlinkFromHashBucket(
|
|
RCACHE * pRCache)
|
|
{
|
|
flmAssert( pRCache->pNewerVersion == NULL);
|
|
if (pRCache->pNextInBucket)
|
|
{
|
|
pRCache->pNextInBucket->pPrevInBucket = pRCache->pPrevInBucket;
|
|
}
|
|
if (pRCache->pPrevInBucket)
|
|
{
|
|
pRCache->pPrevInBucket->pNextInBucket = pRCache->pNextInBucket;
|
|
}
|
|
else
|
|
{
|
|
RCACHE ** ppHashBucket = FLM_RCA_HASH( pRCache->uiDrn);
|
|
*ppHashBucket = pRCache->pNextInBucket;
|
|
}
|
|
pRCache->pPrevInBucket = pRCache->pNextInBucket = NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine unlinks a record from its version list. This routine
|
|
assumes that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaLinkToVerList(
|
|
RCACHE * pRCache,
|
|
RCACHE * pNewerVer,
|
|
RCACHE * pOlderVer)
|
|
{
|
|
if( (pRCache->pNewerVersion = pNewerVer) != NULL)
|
|
{
|
|
pNewerVer->pOlderVersion = pRCache;
|
|
}
|
|
if ((pRCache->pOlderVersion = pOlderVer) != NULL)
|
|
{
|
|
pOlderVer->pNewerVersion = pRCache;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine unlinks a record from its version list. This routine
|
|
assumes that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FINLINE void flmRcaUnlinkFromVerList(
|
|
RCACHE * pRCache)
|
|
{
|
|
if (pRCache->pNewerVersion)
|
|
{
|
|
pRCache->pNewerVersion->pOlderVersion = pRCache->pOlderVersion;
|
|
}
|
|
if (pRCache->pOlderVersion)
|
|
{
|
|
pRCache->pOlderVersion->pNewerVersion = pRCache->pNewerVersion;
|
|
}
|
|
pRCache->pNewerVersion = pRCache->pOlderVersion = NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine frees a purged from record cache. This routine assumes
|
|
that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FSTATIC void flmRcaFreePurged(
|
|
RCACHE * pRCache)
|
|
{
|
|
FLMUINT uiTotalMemory;
|
|
FLMUINT uiFreeMemory;
|
|
|
|
// Release the record data object we are pointing to.
|
|
|
|
if (pRCache->pRecord)
|
|
{
|
|
if( RCA_IS_IN_HEAP_LIST( pRCache->uiFlags))
|
|
{
|
|
flmRcaUnlinkFromHeapList( pRCache);
|
|
}
|
|
|
|
uiTotalMemory = pRCache->pRecord->getTotalMemory();
|
|
uiFreeMemory = pRCache->pRecord->getFreeMemory();
|
|
flmAssert( uiTotalMemory >= uiFreeMemory);
|
|
FlmRecordExt::clearCached( pRCache->pRecord);
|
|
FlmRecordExt::Release( pRCache->pRecord, TRUE);
|
|
pRCache->pRecord = NULL;
|
|
}
|
|
else
|
|
{
|
|
uiTotalMemory = 0;
|
|
uiFreeMemory = 0;
|
|
}
|
|
|
|
// If this is an old version, decrement the old version counters.
|
|
|
|
if (pRCache->uiHighTransId != 0xFFFFFFFF)
|
|
{
|
|
FLMUINT uiTotalOldMemory = uiTotalMemory + sizeof( RCACHE);
|
|
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes >=
|
|
uiTotalOldMemory);
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount);
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes -= uiTotalOldMemory;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount--;
|
|
}
|
|
|
|
// Unlink the RCACHE from the purged list.
|
|
|
|
flmRcaUnlinkFromPurged( pRCache);
|
|
|
|
// Free the RCACHE structure.
|
|
|
|
RCA_UNSET_PURGED( pRCache->uiFlags);
|
|
flmRcaFreeCacheStruct( &pRCache);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine frees a record in the record cache. This routine assumes
|
|
that the record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FSTATIC void flmRcaFreeCache(
|
|
RCACHE * pRCache,
|
|
FLMBOOL bPutInPurgeList)
|
|
{
|
|
FLMUINT uiTotalMemory;
|
|
FLMUINT uiFreeMemory;
|
|
FLMBOOL bOldVersion;
|
|
#ifdef FLM_DBG_LOG
|
|
char szTmpBuf[ 80];
|
|
#endif
|
|
|
|
// Release the record data object we are pointing to.
|
|
|
|
if (pRCache->pRecord && !bPutInPurgeList)
|
|
{
|
|
if( RCA_IS_IN_HEAP_LIST( pRCache->uiFlags))
|
|
{
|
|
flmRcaUnlinkFromHeapList( pRCache);
|
|
}
|
|
|
|
uiTotalMemory = pRCache->pRecord->getTotalMemory();
|
|
uiFreeMemory = pRCache->pRecord->getFreeMemory();
|
|
flmAssert( uiTotalMemory >= uiFreeMemory);
|
|
FlmRecordExt::clearCached( pRCache->pRecord);
|
|
FlmRecordExt::Release( pRCache->pRecord, TRUE);
|
|
pRCache->pRecord = NULL;
|
|
}
|
|
else
|
|
{
|
|
uiTotalMemory = 0;
|
|
uiFreeMemory = 0;
|
|
}
|
|
bOldVersion = (FLMBOOL)((pRCache->uiHighTransId != 0xFFFFFFFF)
|
|
? TRUE
|
|
: FALSE);
|
|
|
|
#ifdef FLM_DBG_LOG
|
|
f_sprintf( szTmpBuf, "RCD:H%X",
|
|
(unsigned)pRCache->uiHighTransId);
|
|
|
|
flmDbgLogWrite( pRCache->pFile ? pRCache->pFile->uiFFileId : 0, pRCache->uiContainer,
|
|
pRCache->uiDrn, pRCache->uiLowTransId, szTmpBuf);
|
|
#endif
|
|
|
|
// Unlink the RCACHE from its various lists.
|
|
|
|
flmRcaUnlinkFromGlobal( pRCache);
|
|
flmRcaUnlinkFromFile( pRCache);
|
|
if (!pRCache->pNewerVersion)
|
|
{
|
|
RCACHE * pOlderVersion = pRCache->pOlderVersion;
|
|
|
|
flmRcaUnlinkFromHashBucket( pRCache);
|
|
|
|
// If there was an older version, it now needs to be
|
|
// put into the hash bucket.
|
|
|
|
if (pOlderVersion)
|
|
{
|
|
flmRcaUnlinkFromVerList( pRCache);
|
|
flmRcaLinkToHashBucket( pOlderVersion);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flmRcaUnlinkFromVerList( pRCache);
|
|
}
|
|
|
|
// Free the RCACHE structure if not putting in purge list.
|
|
|
|
if (!bPutInPurgeList)
|
|
{
|
|
// If this was an older version, decrement the old version counters.
|
|
|
|
if (bOldVersion)
|
|
{
|
|
FLMUINT uiTotalOldMemory = uiTotalMemory + sizeof( RCACHE);
|
|
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes >=
|
|
uiTotalOldMemory);
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount);
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount--;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes -=
|
|
uiTotalOldMemory;
|
|
}
|
|
flmRcaFreeCacheStruct( &pRCache);
|
|
}
|
|
else
|
|
{
|
|
if ((pRCache->pNextInGlobal = gv_FlmSysData.RCacheMgr.pPurgeList) != NULL)
|
|
{
|
|
pRCache->pNextInGlobal->pPrevInGlobal = pRCache;
|
|
}
|
|
gv_FlmSysData.RCacheMgr.pPurgeList = pRCache;
|
|
RCA_SET_PURGED( pRCache->uiFlags);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine initializes record cache manager.
|
|
****************************************************************************/
|
|
RCODE flmRcaInit(
|
|
FLMUINT uiMaxRecordCacheBytes)
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
|
|
f_memset( &gv_FlmSysData.RCacheMgr, 0, sizeof( RCACHE_MGR));
|
|
gv_FlmSysData.RCacheMgr.Usage.uiMaxBytes = uiMaxRecordCacheBytes;
|
|
gv_FlmSysData.RCacheMgr.hMutex = F_MUTEX_NULL;
|
|
|
|
// Allocate the hash buckets.
|
|
|
|
if (RC_BAD( rc = f_calloc(
|
|
(FLMUINT)sizeof( RCACHE *) *
|
|
(FLMUINT)MIN_RCACHE_BUCKETS,
|
|
&gv_FlmSysData.RCacheMgr.ppHashBuckets)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets = MIN_RCACHE_BUCKETS;
|
|
gv_FlmSysData.RCacheMgr.uiHashMask =
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets - 1;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiTotalBytesAllocated +=
|
|
(sizeof( RCACHE *) * gv_FlmSysData.RCacheMgr.uiNumBuckets);
|
|
|
|
// Allocate the mutex for controlling access to the
|
|
// record cache.
|
|
|
|
if (RC_BAD( rc = f_mutexCreate( &gv_FlmSysData.RCacheMgr.hMutex)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Set up the RCACHE struct allocator
|
|
|
|
if( (gv_FlmSysData.RCacheMgr.pRCacheAlloc = f_new F_FixedAlloc) == NULL)
|
|
{
|
|
rc = RC_SET( FERR_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( rc = gv_FlmSysData.RCacheMgr.pRCacheAlloc->setup(
|
|
gv_FlmSysData.pSlabManager, TRUE, sizeof( RCACHE),
|
|
&gv_FlmSysData.RCacheMgr.Usage.uiTotalBytesAllocated)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->setRelocationFuncs(
|
|
rcaCanRelocate, rcaRelocate);
|
|
|
|
// Set up the record object allocator
|
|
|
|
if( (gv_FlmSysData.RCacheMgr.pRecAlloc = f_new F_FixedAlloc) == NULL)
|
|
{
|
|
rc = RC_SET( FERR_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( rc = gv_FlmSysData.RCacheMgr.pRecAlloc->setup(
|
|
gv_FlmSysData.pSlabManager,
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->getMutex(),
|
|
sizeof( FlmRecord),
|
|
&gv_FlmSysData.RCacheMgr.Usage.uiTotalBytesAllocated)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
gv_FlmSysData.RCacheMgr.pRecAlloc->setRelocationFuncs(
|
|
FlmRecordExt::canRelocateRec, FlmRecordExt::relocateRec);
|
|
|
|
// Set up the record buffer allocator
|
|
|
|
if( (gv_FlmSysData.RCacheMgr.pRecBufAlloc = f_new F_BufferAlloc) == NULL)
|
|
{
|
|
rc = RC_SET( FERR_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
if( RC_BAD( rc = gv_FlmSysData.RCacheMgr.pRecBufAlloc->setup(
|
|
gv_FlmSysData.pSlabManager,
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->getMutex(),
|
|
&gv_FlmSysData.RCacheMgr.Usage.uiTotalBytesAllocated)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
gv_FlmSysData.RCacheMgr.pRecBufAlloc->setRelocationFuncs(
|
|
FlmRecordExt::canRelocateRecBuffer,
|
|
FlmRecordExt::relocateRecBuffer);
|
|
|
|
#ifdef FLM_DEBUG
|
|
gv_FlmSysData.RCacheMgr.bDebug = TRUE;
|
|
#endif
|
|
|
|
Exit:
|
|
if (RC_BAD( rc))
|
|
{
|
|
flmRcaExit();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine determines what hash table size best fits the current
|
|
record count. It finds the hash bucket size whose midpoint between
|
|
the minimum and maximum range is closest to the record count.
|
|
****************************************************************************/
|
|
FSTATIC FLMUINT flmRcaGetBestHashTblSize(
|
|
FLMUINT uiCurrRecCount)
|
|
{
|
|
FLMUINT uiHashTblSize;
|
|
FLMUINT uiMaxRecsForHashTblSize;
|
|
FLMUINT uiMinRecsForHashTblSize;
|
|
FLMUINT uiClosestHashTblSize = 0;
|
|
FLMUINT uiDistanceFromMidpoint;
|
|
FLMUINT uiLowestDistanceFromMidpoint;
|
|
FLMUINT uiHashTblRecsMidpoint;
|
|
|
|
uiLowestDistanceFromMidpoint = 0xFFFFFFFF;
|
|
for (uiHashTblSize = MIN_RCACHE_BUCKETS;
|
|
uiHashTblSize <= MAX_RCACHE_BUCKETS;
|
|
uiHashTblSize *= 2)
|
|
{
|
|
|
|
// Maximum desirable record count for a specific hash table size
|
|
// we have arbitrarily chosen to be four times the number of buckets.
|
|
// Minimum desirable record count we have arbitrarily chosen to be
|
|
// the hash table size divided by four.
|
|
|
|
uiMaxRecsForHashTblSize = FLM_RCA_MAX_REC_CNT( uiHashTblSize);
|
|
uiMinRecsForHashTblSize = FLM_RCA_MIN_REC_CNT( uiHashTblSize);
|
|
|
|
// Ignore any hash bucket sizes where the current record count
|
|
// is not between the desired minimum and maximum.
|
|
|
|
if (uiCurrRecCount >= uiMinRecsForHashTblSize &&
|
|
uiCurrRecCount <= uiMaxRecsForHashTblSize)
|
|
{
|
|
|
|
// Calculate the midpoint between the minimum and maximum
|
|
// for this particular hash table size.
|
|
|
|
uiHashTblRecsMidpoint = (uiMaxRecsForHashTblSize -
|
|
uiMinRecsForHashTblSize) / 2;
|
|
|
|
// See how far our current record count is from this midpoint.
|
|
|
|
uiDistanceFromMidpoint = (FLMUINT)((uiHashTblRecsMidpoint > uiCurrRecCount)
|
|
? (uiHashTblRecsMidpoint - uiCurrRecCount)
|
|
: (uiCurrRecCount - uiHashTblRecsMidpoint));
|
|
|
|
// If the distance from the midpoint is closer than our previous
|
|
// lowest distance, save it.
|
|
|
|
if (uiDistanceFromMidpoint < uiLowestDistanceFromMidpoint)
|
|
{
|
|
uiClosestHashTblSize = uiHashTblSize;
|
|
uiLowestDistanceFromMidpoint = uiDistanceFromMidpoint;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Take the number of buckets whose middle was closest to the
|
|
// current record count;
|
|
|
|
if (uiLowestDistanceFromMidpoint == 0xFFFFFFFF)
|
|
{
|
|
// If we did not fall between any of the minimums or maximums,
|
|
// we are either below the lowest minimum, or higher than the
|
|
// highest maximum.
|
|
|
|
uiHashTblSize = (FLMUINT)((uiCurrRecCount < FLM_RCA_MIN_REC_CNT( MIN_RCACHE_BUCKETS))
|
|
? (FLMUINT)MIN_RCACHE_BUCKETS
|
|
: (FLMUINT)MAX_RCACHE_BUCKETS);
|
|
|
|
}
|
|
else
|
|
{
|
|
uiHashTblSize = uiClosestHashTblSize;
|
|
}
|
|
return( uiHashTblSize);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine resizes the hash table for the record cache manager.
|
|
NOTE: This routine assumes that the record cache mutex has been locked.
|
|
****************************************************************************/
|
|
FSTATIC RCODE flmRcaRehash( void)
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
FLMUINT uiNewHashTblSize;
|
|
RCACHE ** ppOldHashTbl;
|
|
FLMUINT uiOldHashTblSize;
|
|
RCACHE ** ppBucket;
|
|
FLMUINT uiLoop;
|
|
RCACHE * pTmpRCache;
|
|
RCACHE * pTmpNextRCache;
|
|
|
|
uiNewHashTblSize = flmRcaGetBestHashTblSize(
|
|
gv_FlmSysData.RCacheMgr.Usage.uiCount);
|
|
|
|
// At this point we better have a different hash table size
|
|
// or something is mucked up!
|
|
|
|
flmAssert( uiNewHashTblSize !=
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets);
|
|
|
|
// Save the old hash table and its size.
|
|
|
|
ppOldHashTbl = gv_FlmSysData.RCacheMgr.ppHashBuckets;
|
|
uiOldHashTblSize = gv_FlmSysData.RCacheMgr.uiNumBuckets;
|
|
|
|
// Allocate a new hash table.
|
|
|
|
if (RC_BAD( rc = f_calloc(
|
|
(FLMUINT)sizeof( RCACHE *) *
|
|
(FLMUINT)uiNewHashTblSize,
|
|
&gv_FlmSysData.RCacheMgr.ppHashBuckets)))
|
|
{
|
|
gv_FlmSysData.RCacheMgr.ppHashBuckets = ppOldHashTbl;
|
|
goto Exit;
|
|
}
|
|
|
|
// Subtract off old size and add in new size.
|
|
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->decrementTotalBytesAllocated(
|
|
(sizeof( RCACHE *) * uiOldHashTblSize));
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->incrementTotalBytesAllocated(
|
|
(sizeof( RCACHE *) * uiNewHashTblSize));
|
|
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets = uiNewHashTblSize;
|
|
gv_FlmSysData.RCacheMgr.uiHashMask = uiNewHashTblSize - 1;
|
|
|
|
// Relink all of the records into the new
|
|
// hash table.
|
|
|
|
for (uiLoop = 0, ppBucket = ppOldHashTbl;
|
|
uiLoop < uiOldHashTblSize;
|
|
uiLoop++, ppBucket++)
|
|
{
|
|
pTmpRCache = *ppBucket;
|
|
while (pTmpRCache)
|
|
{
|
|
pTmpNextRCache = pTmpRCache->pNextInBucket;
|
|
flmRcaLinkToHashBucket( pTmpRCache);
|
|
pTmpRCache = pTmpNextRCache;
|
|
}
|
|
}
|
|
|
|
// Throw away the old hash table.
|
|
|
|
f_free( &ppOldHashTbl);
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine changes the cache size for the record cache manager.
|
|
If necessary, it will resize the hash table. NOTE: This routine
|
|
assumes that the record cache mutex has been locked.
|
|
****************************************************************************/
|
|
FSTATIC RCODE flmRcaSetMemLimit(
|
|
FLMUINT uiMaxCacheBytes)
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
|
|
// If we are shrinking the maximum cache, clean up and
|
|
// defragment cache first
|
|
|
|
gv_FlmSysData.RCacheMgr.Usage.uiMaxBytes = uiMaxCacheBytes;
|
|
if (gv_FlmSysData.RCacheMgr.pRCacheAlloc->getTotalBytesAllocated() >
|
|
uiMaxCacheBytes)
|
|
{
|
|
flmRcaCleanupCache( ~((FLMUINT)0), TRUE);
|
|
}
|
|
|
|
// If the current record count is below the minimum records for the
|
|
// number of buckets or is greater than the maximum records for the
|
|
// number of buckets, we want to resize the hash table.
|
|
|
|
if ((gv_FlmSysData.RCacheMgr.Usage.uiCount >
|
|
FLM_RCA_MAX_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets < MAX_RCACHE_BUCKETS) ||
|
|
(gv_FlmSysData.RCacheMgr.Usage.uiCount <
|
|
FLM_RCA_MIN_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets > MIN_RCACHE_BUCKETS))
|
|
{
|
|
if (RC_BAD( rc = flmRcaRehash()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine configures the record cache manager. NOTE: This routine
|
|
assumes that the record cache mutex has been locked.
|
|
****************************************************************************/
|
|
RCODE flmRcaConfig(
|
|
FLMUINT uiType,
|
|
void * Value1,
|
|
void * Value2)
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
|
|
F_UNREFERENCED_PARM( Value2);
|
|
|
|
switch (uiType)
|
|
{
|
|
case FLM_CACHE_LIMIT:
|
|
rc = flmRcaSetMemLimit( (FLMUINT)Value1);
|
|
break;
|
|
case FLM_SCACHE_DEBUG:
|
|
#ifdef FLM_DEBUG
|
|
gv_FlmSysData.RCacheMgr.bDebug = (FLMBOOL)(Value1 ?
|
|
(FLMBOOL)TRUE : (FLMBOOL)FALSE);
|
|
#endif
|
|
break;
|
|
default:
|
|
rc = RC_SET( FERR_NOT_IMPLEMENTED);
|
|
goto Exit;
|
|
}
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine shuts down the record cache manager and frees all
|
|
resources allocated by it.
|
|
****************************************************************************/
|
|
void flmRcaExit( void)
|
|
{
|
|
if (gv_FlmSysData.RCacheMgr.hMutex != F_MUTEX_NULL)
|
|
{
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
// Free all of the record cache objects.
|
|
|
|
while (gv_FlmSysData.RCacheMgr.pMRURecord)
|
|
{
|
|
f_yieldCPU();
|
|
flmRcaFreeCache( gv_FlmSysData.RCacheMgr.pMRURecord, FALSE);
|
|
}
|
|
|
|
// Must free those in the purge list too.
|
|
|
|
while (gv_FlmSysData.RCacheMgr.pPurgeList)
|
|
{
|
|
f_yieldCPU();
|
|
flmRcaFreePurged( gv_FlmSysData.RCacheMgr.pPurgeList);
|
|
}
|
|
|
|
// The math better be consistent!
|
|
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiCount == 0);
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount == 0);
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes == 0);
|
|
|
|
// Free the hash bucket array
|
|
|
|
if (gv_FlmSysData.RCacheMgr.ppHashBuckets)
|
|
{
|
|
f_free( &gv_FlmSysData.RCacheMgr.ppHashBuckets);
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->decrementTotalBytesAllocated(
|
|
(sizeof( RCACHE *) * gv_FlmSysData.RCacheMgr.uiNumBuckets));
|
|
}
|
|
|
|
// Free the mutex that controls access to record cache.
|
|
// NOTE: This should be done last so that the mutex is not
|
|
// unlocked until the very end.
|
|
|
|
if (gv_FlmSysData.RCacheMgr.hMutex != F_MUTEX_NULL)
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
f_mutexDestroy( &gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
// Free the allocators
|
|
|
|
if( gv_FlmSysData.RCacheMgr.pRecBufAlloc)
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pRecBufAlloc->Release();
|
|
gv_FlmSysData.RCacheMgr.pRecBufAlloc = NULL;
|
|
}
|
|
|
|
if( gv_FlmSysData.RCacheMgr.pRecAlloc)
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pRecAlloc->Release();
|
|
gv_FlmSysData.RCacheMgr.pRecAlloc = NULL;
|
|
}
|
|
|
|
if( gv_FlmSysData.RCacheMgr.pRCacheAlloc)
|
|
{
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->Release();
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc = NULL;
|
|
}
|
|
|
|
// Zero the entire structure out, just for good measure.
|
|
|
|
f_memset( &gv_FlmSysData.RCacheMgr, 0, sizeof( RCACHE_MGR));
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a notify request into a notification list and
|
|
then waits to be notified that the event has occurred.
|
|
NOTE: This routine assumes that the record cache mutex is locked and that
|
|
it is supposed to unlock it. It will relock the mutex on its way out.
|
|
****************************************************************************/
|
|
FSTATIC RCODE flmRcaWaitNotify(
|
|
FNOTIFY ** ppNotifyListRV)
|
|
{
|
|
FNOTIFY * pNotify = NULL;
|
|
RCODE TempRc;
|
|
RCODE rc = FERR_OK;
|
|
F_SEM hSem;
|
|
|
|
// First create a notify request and link it into the list.
|
|
|
|
if (RC_OK( rc = f_calloc( (FLMUINT)(sizeof( FNOTIFY)), &pNotify)))
|
|
{
|
|
// Allocate a semaphore for the notify request.
|
|
|
|
pNotify->uiThreadId = f_threadId();
|
|
pNotify->hSem = F_SEM_NULL;
|
|
rc = f_semCreate( &pNotify->hSem);
|
|
}
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
if (pNotify)
|
|
{
|
|
if (pNotify->hSem != F_SEM_NULL)
|
|
{
|
|
f_semDestroy( &pNotify->hSem);
|
|
}
|
|
|
|
f_free( &pNotify);
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
pNotify->pRc = &rc;
|
|
pNotify->UserData = NULL;
|
|
pNotify->pNext = *ppNotifyListRV;
|
|
*ppNotifyListRV = pNotify;
|
|
hSem = pNotify->hSem;
|
|
|
|
// Unlock the mutex and wait on the semaphore.
|
|
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
if (RC_BAD( TempRc = f_semWait( hSem, F_SEM_WAITFOREVER)))
|
|
{
|
|
rc = TempRc;
|
|
}
|
|
|
|
// Free the semaphore and the notify structure.
|
|
|
|
f_semDestroy( &hSem);
|
|
f_free( &pNotify);
|
|
|
|
// Relock the record cache mutex
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine notifies threads waiting for a pending read to complete.
|
|
NOTE: This routine assumes that the record cache mutex is already
|
|
locked.
|
|
****************************************************************************/
|
|
FSTATIC void flmRcaNotify(
|
|
FNOTIFY * pNotify,
|
|
RCACHE * pUseRCache,
|
|
RCODE NotifyRc)
|
|
{
|
|
while (pNotify)
|
|
{
|
|
F_SEM hSem;
|
|
|
|
*(pNotify->pRc) = NotifyRc;
|
|
if (RC_OK( NotifyRc))
|
|
{
|
|
RCA_INCR_USE_COUNT( pUseRCache->uiFlags);
|
|
}
|
|
hSem = pNotify->hSem;
|
|
pNotify = pNotify->pNext;
|
|
f_semSignal( hSem);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Allocate a new record cache structure. This routine assumes that the
|
|
record cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FSTATIC RCODE flmRcaAllocCacheStruct(
|
|
RCACHE ** ppRCache)
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
|
|
if( (*ppRCache =
|
|
(RCACHE *)gv_FlmSysData.RCacheMgr.pRCacheAlloc->allocCell()) == NULL)
|
|
{
|
|
rc = RC_SET( FERR_MEM);
|
|
goto Exit;
|
|
}
|
|
|
|
f_memset( *ppRCache, 0, sizeof( RCACHE));
|
|
|
|
// Increment the total records cached
|
|
|
|
gv_FlmSysData.RCacheMgr.Usage.uiCount++;
|
|
|
|
// Set the high transaction ID to 0xFFFFFFFF so that this will NOT
|
|
// be treated as one that had memory assigned to the old records.
|
|
|
|
(*ppRCache)->uiHighTransId = 0xFFFFFFFF;
|
|
|
|
Exit:
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Free a record cache structure. This routine assumes that the record
|
|
cache mutex has already been locked.
|
|
****************************************************************************/
|
|
FSTATIC void flmRcaFreeCacheStruct(
|
|
RCACHE ** ppRCache)
|
|
{
|
|
flmAssert( !RCA_IS_IN_HEAP_LIST( (*ppRCache)->uiFlags));
|
|
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->freeCell( *ppRCache);
|
|
*ppRCache = NULL;
|
|
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiCount > 0);
|
|
gv_FlmSysData.RCacheMgr.Usage.uiCount--;
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Cleanup old records in cache that are no longer needed by any
|
|
transaction.
|
|
****************************************************************************/
|
|
void flmRcaCleanupCache(
|
|
FLMUINT uiMaxLockTime,
|
|
FLMBOOL bMutexesLocked)
|
|
{
|
|
RCACHE * pTmpRCache;
|
|
RCACHE * pPrevRCache;
|
|
RCACHE * pNextRCache;
|
|
FLMUINT uiRecordsExamined = 0;
|
|
FLMUINT uiLastTimePaused = FLM_GET_TIMER();
|
|
FLMUINT uiCurrTime;
|
|
FLMBOOL bUnlockMutexes = FALSE;
|
|
|
|
if( !bMutexesLocked)
|
|
{
|
|
f_mutexLock( gv_FlmSysData.hShareMutex);
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
bUnlockMutexes = TRUE;
|
|
}
|
|
|
|
// Try to free everything in the heap list
|
|
|
|
pTmpRCache = gv_FlmSysData.RCacheMgr.pHeapList;
|
|
|
|
while( pTmpRCache)
|
|
{
|
|
uiRecordsExamined++;
|
|
|
|
// Save the pointer to the next entry in the list because
|
|
// we may end up unlinking pTmpRCache below
|
|
|
|
pNextRCache = pTmpRCache->pNextInHeapList;
|
|
|
|
// Determine if the item can be freed
|
|
|
|
flmAssert( RCA_IS_IN_HEAP_LIST( pTmpRCache->uiFlags));
|
|
|
|
if( !RCA_IS_IN_USE( pTmpRCache->uiFlags) &&
|
|
!RCA_IS_READING_IN( pTmpRCache->uiFlags))
|
|
{
|
|
flmRcaFreeCache( pTmpRCache, FALSE);
|
|
}
|
|
|
|
pTmpRCache = pNextRCache;
|
|
}
|
|
|
|
// Now, free any old versions that are no longer needed
|
|
|
|
flmRcaReduceCache( TRUE);
|
|
pTmpRCache = gv_FlmSysData.RCacheMgr.pLRURecord;
|
|
|
|
// Stay in the loop until we have freed all old records, or
|
|
// we have run through the entire list.
|
|
|
|
for( ;;)
|
|
{
|
|
if( !pTmpRCache || !gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// After each 200 records examined, see if our maximum
|
|
// time has elapsed for examining without a pause.
|
|
|
|
if( uiRecordsExamined >= 200)
|
|
{
|
|
uiRecordsExamined = 0;
|
|
uiCurrTime = FLM_GET_TIMER();
|
|
|
|
if( FLM_ELAPSED_TIME( uiCurrTime, uiLastTimePaused) >= uiMaxLockTime)
|
|
{
|
|
// IMPORTANT! Don't stop and pause on one that is
|
|
// being read in.
|
|
|
|
while( pTmpRCache && RCA_IS_READING_IN( pTmpRCache->uiFlags))
|
|
{
|
|
pTmpRCache = pTmpRCache->pPrevInGlobal;
|
|
}
|
|
|
|
if( !pTmpRCache)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if( bUnlockMutexes)
|
|
{
|
|
// Increment the use count so that this item will not
|
|
// go away while we are paused.
|
|
|
|
RCA_INCR_USE_COUNT( pTmpRCache->uiFlags);
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
f_mutexUnlock( gv_FlmSysData.hShareMutex);
|
|
|
|
// Shortest possible pause - to allow other threads
|
|
// to do work.
|
|
|
|
#ifdef FLM_NLM
|
|
f_yieldCPU();
|
|
#else
|
|
f_sleep( 0);
|
|
#endif
|
|
|
|
// Relock the mutexes
|
|
|
|
uiLastTimePaused = FLM_GET_TIMER();
|
|
f_mutexLock( gv_FlmSysData.hShareMutex);
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
|
|
// Decrement use count that was added above.
|
|
|
|
RCA_DECR_USE_COUNT( pTmpRCache->uiFlags);
|
|
}
|
|
|
|
// If the item was purged while we were paused,
|
|
// finish the job and then start again at the
|
|
// top of the list.
|
|
|
|
if( RCA_IS_PURGED( pTmpRCache->uiFlags))
|
|
{
|
|
if( !RCA_IS_IN_USE( pTmpRCache->uiFlags))
|
|
{
|
|
flmRcaFreePurged( pTmpRCache);
|
|
}
|
|
|
|
pTmpRCache = gv_FlmSysData.RCacheMgr.pLRURecord;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
uiRecordsExamined++;
|
|
|
|
// Save the pointer to the previous entry in the list because
|
|
// we may end up unlinking pTmpRCache below, in which case we would
|
|
// have lost the previous entry.
|
|
|
|
pPrevRCache = pTmpRCache->pPrevInGlobal;
|
|
|
|
// Block must not currently be in use,
|
|
// Must not be the most current version of a block,
|
|
// Cannot be dirty in any way,
|
|
// Cannot be in the process of being read in from disk,
|
|
// And must not be needed by a read transaction.
|
|
|
|
if( !RCA_IS_IN_USE( pTmpRCache->uiFlags) &&
|
|
!RCA_IS_READING_IN( pTmpRCache->uiFlags) &&
|
|
(RCA_IS_IN_HEAP_LIST( pTmpRCache->uiFlags) ||
|
|
(pTmpRCache->uiHighTransId != 0xFFFFFFFF &&
|
|
!flmNeededByReadTrans( pTmpRCache->pFile,
|
|
pTmpRCache->uiLowTransId,
|
|
pTmpRCache->uiHighTransId))))
|
|
{
|
|
flmRcaFreeCache( pTmpRCache, FALSE);
|
|
}
|
|
|
|
pTmpRCache = pPrevRCache;
|
|
}
|
|
|
|
// Defragment memory
|
|
|
|
gv_FlmSysData.RCacheMgr.pRCacheAlloc->defragmentMemory();
|
|
gv_FlmSysData.RCacheMgr.pRecAlloc->defragmentMemory();
|
|
gv_FlmSysData.RCacheMgr.pRecBufAlloc->defragmentMemory();
|
|
|
|
// Unlock the mutexes
|
|
|
|
if( bUnlockMutexes)
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
f_mutexUnlock( gv_FlmSysData.hShareMutex);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine reduces record cache down to the limit expected.
|
|
****************************************************************************/
|
|
void flmRcaReduceCache(
|
|
FLMBOOL bMutexAlreadyLocked)
|
|
{
|
|
RCACHE * pRCache;
|
|
RCACHE * pPrevRCache;
|
|
|
|
// Make sure the mutex is locked.
|
|
|
|
if (!bMutexAlreadyLocked)
|
|
{
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
pRCache = gv_FlmSysData.RCacheMgr.pLRURecord;
|
|
|
|
// Free things until we get down below our memory limit.
|
|
|
|
while( gv_FlmSysData.RCacheMgr.pRCacheAlloc->getTotalBytesAllocated() >
|
|
gv_FlmSysData.RCacheMgr.Usage.uiMaxBytes)
|
|
{
|
|
if( !pRCache)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If the total of block and record cache is below the global
|
|
// cache maximum, there is no need to reduce record cache.
|
|
|
|
if( (gv_FlmSysData.RCacheMgr.pRCacheAlloc->getTotalBytesAllocated() +
|
|
gv_FlmSysData.SCacheMgr.Usage.uiTotalBytesAllocated) <=
|
|
gv_FlmSysData.uiMaxCache)
|
|
{
|
|
break;
|
|
}
|
|
|
|
pPrevRCache = pRCache->pPrevInGlobal;
|
|
if (!(RCA_IS_IN_USE( pRCache->uiFlags)) &&
|
|
!(RCA_IS_READING_IN( pRCache->uiFlags)))
|
|
{
|
|
flmRcaFreeCache( pRCache, FALSE);
|
|
}
|
|
pRCache = pPrevRCache;
|
|
}
|
|
|
|
if (!bMutexAlreadyLocked)
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine finds a record in the record cache. If it cannot
|
|
find the record, it will return the position where the record should
|
|
be inserted. NOTE: This routine assumes that the record cache mutex
|
|
has been locked.
|
|
****************************************************************************/
|
|
void flmRcaFindRec(
|
|
FLMUINT uiContainer,
|
|
FLMUINT uiDrn,
|
|
FFILE_p pFile,
|
|
FLMUINT uiVersionNeeded,
|
|
FLMBOOL bDontPoisonCache,
|
|
FLMUINT * puiNumLooks,
|
|
RCACHE ** ppRCache,
|
|
RCACHE ** ppNewerRCache,
|
|
RCACHE ** ppOlderRCache)
|
|
{
|
|
RCACHE * pRCache;
|
|
FLMUINT uiNumLooks = 0;
|
|
FLMBOOL bFound;
|
|
RCACHE * pNewerRCache;
|
|
RCACHE * pOlderRCache;
|
|
|
|
// Search down the hash bucket for the matching item.
|
|
|
|
Start_Find:
|
|
|
|
// NOTE: Need to always calculate hash bucket because
|
|
// the hash table may have been changed while we
|
|
// were waiting to be notified below - mutex can
|
|
// be unlocked, but it is guaranteed to be locked
|
|
// here.
|
|
|
|
pRCache = *(FLM_RCA_HASH( uiDrn));
|
|
bFound = FALSE;
|
|
uiNumLooks = 1;
|
|
while ((pRCache) &&
|
|
((pRCache->uiDrn != uiDrn) ||
|
|
(pRCache->uiContainer != uiContainer) ||
|
|
(pRCache->pFile != pFile)))
|
|
{
|
|
if ((pRCache = pRCache->pNextInBucket) != NULL)
|
|
{
|
|
uiNumLooks++;
|
|
}
|
|
}
|
|
|
|
// If we found the record, see if we have the right version.
|
|
|
|
if (!pRCache)
|
|
{
|
|
pNewerRCache = pOlderRCache = NULL;
|
|
}
|
|
else
|
|
{
|
|
pNewerRCache = NULL;
|
|
pOlderRCache = pRCache;
|
|
for (;;)
|
|
{
|
|
|
|
// If this one is being read in, we need to wait on it.
|
|
|
|
if (RCA_IS_READING_IN( pRCache->uiFlags))
|
|
{
|
|
// We need to wait for this record to be read in
|
|
// in case it coalesces with other versions, resulting
|
|
// in a version that satisfies our request.
|
|
|
|
gv_FlmSysData.RCacheMgr.uiIoWaits++;
|
|
if (RC_BAD( flmRcaWaitNotify( &pRCache->pNotifyList)))
|
|
{
|
|
|
|
// Don't care what error the other thread had reading
|
|
// the thing in from disk - we'll bail out and start
|
|
// our find again.
|
|
|
|
goto Start_Find;
|
|
}
|
|
|
|
// The thread doing the notify "uses" the record cache
|
|
// on behalf of this thread to prevent the record
|
|
// from being replaced after it unlocks the mutex.
|
|
// At this point, since we have locked the mutex,
|
|
// we need to release the record - because we
|
|
// will put a "use" on it below.
|
|
|
|
RCA_DECR_USE_COUNT( pRCache->uiFlags);
|
|
|
|
if (RCA_IS_PURGED( pRCache->uiFlags))
|
|
{
|
|
if (!RCA_IS_IN_USE( pRCache->uiFlags))
|
|
{
|
|
flmRcaFreePurged( pRCache);
|
|
}
|
|
}
|
|
|
|
// Start over with the find because the list
|
|
// structure has changed.
|
|
|
|
goto Start_Find;
|
|
}
|
|
|
|
// See if this record version is the one we need.
|
|
|
|
if (uiVersionNeeded < pRCache->uiLowTransId)
|
|
{
|
|
pNewerRCache = pRCache;
|
|
if ((pOlderRCache = pRCache = pRCache->pOlderVersion) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
uiNumLooks++;
|
|
}
|
|
else if (uiVersionNeeded <= pRCache->uiHighTransId)
|
|
{
|
|
// Make this the MRU record.
|
|
|
|
if (puiNumLooks)
|
|
{
|
|
if (bDontPoisonCache)
|
|
{
|
|
flmRcaStepUpInGlobalList( pRCache);
|
|
}
|
|
else if (pRCache->pPrevInGlobal)
|
|
{
|
|
flmRcaUnlinkFromGlobal( pRCache);
|
|
flmRcaLinkToGlobalAsMRU( pRCache);
|
|
}
|
|
|
|
gv_FlmSysData.RCacheMgr.Usage.uiCacheHits++;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiCacheHitLooks += uiNumLooks;
|
|
}
|
|
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
pOlderRCache = pRCache;
|
|
pNewerRCache = pRCache->pNewerVersion;
|
|
|
|
// Set pRCache to NULL as an indicator that we did not
|
|
// find the version we needed.
|
|
|
|
pRCache = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
*ppRCache = pRCache;
|
|
*ppOlderRCache = pOlderRCache;
|
|
*ppNewerRCache = pNewerRCache;
|
|
|
|
if (puiNumLooks)
|
|
{
|
|
*puiNumLooks = uiNumLooks;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine replaces the FlmRecord that a record is pointing to
|
|
with a new one. NOTE: This routine assumes that the record cache
|
|
mutex is already locked.
|
|
****************************************************************************/
|
|
FSTATIC void flmRcaSetRecord(
|
|
RCACHE * pRCache,
|
|
FlmRecord * pNewRecord)
|
|
{
|
|
FLMUINT uiTotalMemory = 0;
|
|
FLMUINT uiFreeMemory;
|
|
FlmRecord * pOldRecord;
|
|
|
|
// Release the cache's pointer to the old record data
|
|
|
|
if ((pOldRecord = pRCache->pRecord) != NULL)
|
|
{
|
|
if( RCA_IS_IN_HEAP_LIST( pRCache->uiFlags))
|
|
{
|
|
flmRcaUnlinkFromHeapList( pRCache);
|
|
}
|
|
|
|
uiTotalMemory = pOldRecord->getTotalMemory();
|
|
uiFreeMemory = pOldRecord->getFreeMemory();
|
|
flmAssert( uiTotalMemory >= uiFreeMemory);
|
|
FlmRecordExt::clearCached( pOldRecord);
|
|
FlmRecordExt::Release( pOldRecord, TRUE);
|
|
pRCache->pRecord = NULL;
|
|
}
|
|
|
|
if (pRCache->uiHighTransId != 0xFFFFFFFF)
|
|
{
|
|
FLMUINT uiTotalOldMemory = uiTotalMemory + sizeof( RCACHE);
|
|
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes >=
|
|
uiTotalOldMemory);
|
|
flmAssert( gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount);
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes -=
|
|
uiTotalOldMemory;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount--;
|
|
}
|
|
|
|
// Point to the new record data.
|
|
|
|
flmAssert( pNewRecord->getID() == pRCache->uiDrn);
|
|
flmAssert( pNewRecord->getContainerID() == pRCache->uiContainer);
|
|
pRCache->pRecord = pNewRecord;
|
|
flmAssert( !pNewRecord->isCached());
|
|
FlmRecordExt::setCached( pNewRecord);
|
|
FlmRecordExt::AddRef( pNewRecord, TRUE);
|
|
FlmRecordExt::setReadOnly( pNewRecord);
|
|
|
|
if( FlmRecordExt::getFlags( pNewRecord) & RCA_HEAP_BUFFER)
|
|
{
|
|
flmRcaLinkToHeapList( pRCache);
|
|
}
|
|
|
|
uiTotalMemory = pNewRecord->getTotalMemory();
|
|
|
|
if (pRCache->uiHighTransId != 0xFFFFFFFF)
|
|
{
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerBytes +=
|
|
(uiTotalMemory + sizeof( RCACHE));
|
|
gv_FlmSysData.RCacheMgr.Usage.uiOldVerCount++;
|
|
}
|
|
uiFreeMemory = pNewRecord->getFreeMemory();
|
|
flmAssert( uiTotalMemory >= uiFreeMemory);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a new RCACHE structure into the global list and
|
|
into the correct place in its hash bucket. This routine assumes that
|
|
the record cache mutex is already locked.
|
|
****************************************************************************/
|
|
FSTATIC void flmRcaLinkIntoRCache(
|
|
RCACHE * pNewerRCache,
|
|
RCACHE * pOlderRCache,
|
|
RCACHE * pRCache,
|
|
FLMBOOL bLinkAsMRU)
|
|
{
|
|
if( bLinkAsMRU)
|
|
{
|
|
flmRcaLinkToGlobalAsMRU( pRCache);
|
|
}
|
|
else
|
|
{
|
|
flmRcaLinkToGlobalAsLRU( pRCache);
|
|
}
|
|
|
|
if (pNewerRCache)
|
|
{
|
|
flmRcaLinkToVerList( pRCache, pNewerRCache, pOlderRCache);
|
|
}
|
|
else
|
|
{
|
|
RCACHE * pNull = NULL;
|
|
|
|
if (pOlderRCache)
|
|
{
|
|
flmRcaUnlinkFromHashBucket( pOlderRCache);
|
|
}
|
|
flmRcaLinkToHashBucket( pRCache);
|
|
flmRcaLinkToVerList( pRCache, pNull, pOlderRCache);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine links a new record to its FFILE according to whether
|
|
or not it is an update transaction or a read transaction.
|
|
It coalesces out any unnecessary versions. This routine assumes
|
|
that the record cache mutex is already locked.
|
|
****************************************************************************/
|
|
FSTATIC void flmRcaLinkToFFILE(
|
|
RCACHE * pRCache,
|
|
FFILE_p pFile,
|
|
FDB_p pDb,
|
|
FLMUINT uiLowTransId,
|
|
FLMBOOL bMostCurrent)
|
|
{
|
|
RCACHE * pTmpRCache;
|
|
#ifdef FLM_DBG_LOG
|
|
char szTmpBuf[ 80];
|
|
#endif
|
|
|
|
|
|
pRCache->uiLowTransId = uiLowTransId;
|
|
|
|
// Before coalescing, link to FFILE.
|
|
// The following test determines if the record is an
|
|
// uncommitted version generated by the update transaction.
|
|
// If so, we mark it as such, and link it at the head of the
|
|
// FFILE list - so we can get rid of it quickly if we abort
|
|
// the transaction.
|
|
|
|
if (flmGetDbTransType( pDb) == FLM_UPDATE_TRANS)
|
|
{
|
|
|
|
// If we are in an update transaction, there better not
|
|
// be any newer versions in the list and the high
|
|
// transaction ID returned better be 0xFFFFFFFF.
|
|
|
|
flmAssert( pRCache->pNewerVersion == NULL);
|
|
flmRcaSetTransID( pRCache, 0xFFFFFFFF);
|
|
|
|
// If the low transaction ID is the same as the transaction,
|
|
// we may have modified this record during the transaction.
|
|
// Unfortunately, there is no sure way to tell, so we are
|
|
// forced to assume it may have been modified. If the
|
|
// transaction aborts, we will get rid if this version out
|
|
// of cache.
|
|
|
|
if (uiLowTransId == pDb->LogHdr.uiCurrTransID)
|
|
{
|
|
RCA_SET_UNCOMMITTED( pRCache->uiFlags);
|
|
flmRcaLinkToFileAtHead( pRCache, pFile);
|
|
}
|
|
else
|
|
{
|
|
RCA_UNSET_UNCOMMITTED( pRCache->uiFlags);
|
|
flmRcaLinkToFileAtEnd( pRCache, pFile);
|
|
}
|
|
#ifdef FLM_DBG_LOG
|
|
f_sprintf( szTmpBuf, "RCI:L%X,H%X",
|
|
(unsigned)pRCache->uiLowTransId,
|
|
(unsigned)pRCache->uiHighTransId);
|
|
|
|
flmDbgLogWrite( pFile->uiFFileId, pRCache->uiContainer, pRCache->uiDrn,
|
|
pDb->LogHdr.uiCurrTransID, szTmpBuf);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Adjust the high transaction ID to be the same as
|
|
// the transaction ID - we may have gotten a 0xFFFFFFF
|
|
// back, but that is possible even if the record is
|
|
// not the most current version. Besides that, it is
|
|
// possible that in the mean time one or more update
|
|
// transactions have come along and created one or
|
|
// more newer versions of the record.
|
|
|
|
FLMUINT uiHighTransId = (FLMUINT)((bMostCurrent)
|
|
? (FLMUINT)0xFFFFFFFF
|
|
: pDb->LogHdr.uiCurrTransID);
|
|
|
|
flmRcaSetTransID( pRCache, uiHighTransId);
|
|
|
|
// For a read transaction, if there is a newer version,
|
|
// it better have a higher "low transaction ID"
|
|
|
|
#ifdef FLM_DBG_LOG
|
|
f_sprintf( szTmpBuf, "RCA:L%X,H%X",
|
|
(unsigned)pRCache->uiLowTransId,
|
|
(unsigned)pRCache->uiHighTransId);
|
|
|
|
flmDbgLogWrite( pFile->uiFFileId, pRCache->uiContainer, pRCache->uiDrn,
|
|
pDb->LogHdr.uiCurrTransID, szTmpBuf);
|
|
#endif
|
|
|
|
#ifdef FLM_DEBUG
|
|
if (pRCache->pNewerVersion &&
|
|
!RCA_IS_READING_IN( pRCache->pNewerVersion->uiFlags))
|
|
{
|
|
flmAssert( pRCache->uiHighTransId <
|
|
pRCache->pNewerVersion->uiLowTransId);
|
|
if( pRCache->uiHighTransId >=
|
|
pRCache->pNewerVersion->uiLowTransId)
|
|
{
|
|
flmRcaCheck( pDb, pRCache->uiContainer, pRCache->uiDrn);
|
|
}
|
|
}
|
|
#endif
|
|
RCA_UNSET_UNCOMMITTED( pRCache->uiFlags);
|
|
flmRcaLinkToFileAtEnd( pRCache, pFile);
|
|
}
|
|
|
|
// Coalesce any versions that overlap - can only
|
|
// coalesce older versions. For an updater, there
|
|
// should not be any newer versions. For a reader, it
|
|
// is impossible to know how high up it can coalesce.
|
|
// The read operation that read the record may have
|
|
// gotten back a 0xFFFFFFFF for its high transaction
|
|
// ID - but after that point in time, it is possible
|
|
// that one or more update transactions may have come
|
|
// along and created one or more newer versions that
|
|
// it would be incorrect to coalesce with.
|
|
// In reality, a read transaction has to ignore the
|
|
// 0xFFFFFFFF in the high transaction ID anyway
|
|
// because there is no way to know if it is correct.
|
|
|
|
// Coalesce older versions.
|
|
|
|
for (;;)
|
|
{
|
|
if ((pTmpRCache = pRCache->pOlderVersion) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Stop if we encounter one that is being read in.
|
|
|
|
if (RCA_IS_READING_IN( pTmpRCache->uiFlags))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If there is no overlap between these two, there is
|
|
// nothing more to coalesce.
|
|
|
|
if (pRCache->uiLowTransId > pTmpRCache->uiHighTransId)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (pRCache->uiHighTransId <= pTmpRCache->uiHighTransId)
|
|
{
|
|
// This assert represents the following case,
|
|
// which should not be possible to hit:
|
|
|
|
// pOlder->uiHighTransId > pRCache->uiHighTransId.
|
|
// This cannot be, because if pOlder has a higher
|
|
// transaction ID, we would have found it up above and
|
|
// not tried to have read it in.
|
|
|
|
flmAssert( 0);
|
|
#ifdef FLM_DEBUG
|
|
flmRcaCheck( pDb, pRCache->uiContainer, pRCache->uiDrn);
|
|
#endif
|
|
}
|
|
else if (pRCache->uiLowTransId >= pTmpRCache->uiLowTransId)
|
|
{
|
|
pRCache->uiLowTransId = pTmpRCache->uiLowTransId;
|
|
flmRcaFreeCache( pTmpRCache,
|
|
(FLMBOOL)((RCA_IS_IN_USE( pTmpRCache->uiFlags) ||
|
|
RCA_IS_READING_IN( pTmpRCache->uiFlags))
|
|
? TRUE
|
|
: FALSE));
|
|
}
|
|
else
|
|
{
|
|
// This assert represents the following case,
|
|
// which should not be possible to hit:
|
|
|
|
// pRCache->uiLowTransId < pOlder->uiLowTransId.
|
|
// This cannot be, because pOlder has to have been read
|
|
// in to memory by a transaction whose transaction ID is
|
|
// less than or equal to our own. That being the case,
|
|
// it would be impossible for our transaction to have
|
|
// found a version of the record that is older than pOlder.
|
|
|
|
flmAssert( 0);
|
|
#ifdef FLM_DEBUG
|
|
flmRcaCheck( pDb, pRCache->uiContainer, pRCache->uiDrn);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine retrieves a record from the record cache.
|
|
****************************************************************************/
|
|
RCODE flmRcaRetrieveRec(
|
|
FDB * pDb,
|
|
FLMBOOL * pbTransStarted,
|
|
FLMUINT uiContainer, // Container record is in.
|
|
FLMUINT uiDrn, // DRN of record.
|
|
FLMBOOL bOkToGetFromDisk, // If not in cache, OK to get from disk?
|
|
BTSK * pStack, // Use stack to retrieve, if NON-NULL.
|
|
LFILE * pLFile, // LFILE to use, if retrieving with stack
|
|
FlmRecord ** ppRecord)
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
FLMBOOL bRCacheMutexLocked = FALSE;
|
|
FFILE_p pFile = pDb->pFile;
|
|
RCACHE * pRCache;
|
|
RCACHE * pNewerRCache;
|
|
RCACHE * pOlderRCache;
|
|
FlmRecord * pRecord = NULL;
|
|
FLMBOOL bGotFromDisk = FALSE;
|
|
FlmRecord * pNewRecord = NULL;
|
|
FlmRecord * pOldRecord = NULL;
|
|
FLMUINT uiLowTransId;
|
|
FLMBOOL bMostCurrent;
|
|
FLMUINT uiCurrTransId;
|
|
FNOTIFY_p pNotify;
|
|
FLMUINT uiNumLooks;
|
|
FLMBOOL bInitializedFdb = FALSE;
|
|
FLMBOOL bDontPoisonCache = pDb->uiFlags & FDB_DONT_POISON_CACHE
|
|
? TRUE
|
|
: FALSE;
|
|
|
|
if( RC_BAD( rc = flmCheckDatabaseState( pDb)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Get the current transaction ID
|
|
|
|
if( pDb->uiTransType != FLM_NO_TRANS)
|
|
{
|
|
uiCurrTransId = pDb->LogHdr.uiCurrTransID;
|
|
}
|
|
else
|
|
{
|
|
flmAssert( pbTransStarted != NULL);
|
|
|
|
f_mutexLock( gv_FlmSysData.hShareMutex);
|
|
|
|
// Get the last committed transaction ID.
|
|
|
|
uiCurrTransId = (FLMUINT)FB2UD(
|
|
&pFile->ucLastCommittedLogHdr[ LOG_CURR_TRANS_ID]);
|
|
|
|
f_mutexUnlock( gv_FlmSysData.hShareMutex);
|
|
}
|
|
|
|
flmAssert( uiDrn != 0);
|
|
|
|
// Lock the mutex
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
bRCacheMutexLocked = TRUE;
|
|
|
|
// Reset the FDB's inactive time
|
|
|
|
pDb->uiInactiveTime = 0;
|
|
|
|
// See if we should resize the hash table.
|
|
|
|
if ((gv_FlmSysData.RCacheMgr.Usage.uiCount >
|
|
FLM_RCA_MAX_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets < MAX_RCACHE_BUCKETS) ||
|
|
(gv_FlmSysData.RCacheMgr.Usage.uiCount <
|
|
FLM_RCA_MIN_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets > MIN_RCACHE_BUCKETS))
|
|
{
|
|
if (RC_BAD( rc = flmRcaRehash()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Start_Find:
|
|
|
|
flmRcaFindRec( uiContainer, uiDrn, pFile,
|
|
uiCurrTransId, bDontPoisonCache,
|
|
&uiNumLooks, &pRCache,
|
|
&pNewerRCache, &pOlderRCache);
|
|
|
|
if (pRCache)
|
|
{
|
|
if( ppRecord)
|
|
{
|
|
goto Found_Record;
|
|
}
|
|
goto Exit;
|
|
}
|
|
|
|
// Did not find the record, fetch from disk, if OK to do so.
|
|
|
|
if (!bOkToGetFromDisk || !ppRecord)
|
|
{
|
|
rc = RC_SET( FERR_NOT_FOUND);
|
|
goto Exit;
|
|
}
|
|
|
|
// Code to handle case where we are not in a transaction.
|
|
// If we are already in a transaction, we will do the
|
|
// call to fdbInit below - AFTER allocating the RCACHE, etc.
|
|
|
|
if( pbTransStarted && pDb->uiTransType == FLM_NO_TRANS)
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
bRCacheMutexLocked = FALSE;
|
|
|
|
if ( RC_BAD( rc = fdbInit( pDb, FLM_READ_TRANS,
|
|
FDB_TRANS_GOING_OK, 0, pbTransStarted)))
|
|
{
|
|
fdbExit( pDb);
|
|
goto Exit;
|
|
}
|
|
bInitializedFdb = TRUE;
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
bRCacheMutexLocked = TRUE;
|
|
|
|
uiCurrTransId = pDb->LogHdr.uiCurrTransID;
|
|
goto Start_Find;
|
|
}
|
|
|
|
// Increment the number of faults only if we retrieve the record from disk.
|
|
|
|
gv_FlmSysData.RCacheMgr.Usage.uiCacheFaults++;
|
|
gv_FlmSysData.RCacheMgr.Usage.uiCacheFaultLooks += uiNumLooks;
|
|
|
|
// Create a place holder for the object.
|
|
|
|
if (RC_BAD( rc = flmRcaAllocCacheStruct( &pRCache)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
pRCache->uiDrn = uiDrn;
|
|
pRCache->uiContainer = uiContainer;
|
|
|
|
// Set the FFILE so that other threads looking for this record in
|
|
// cache will find it and wait until the read has completed. If
|
|
// the FFILE is not set, other threads will attempt their own read,
|
|
// because they won't match a NULL FFILE. The result of not setting
|
|
// the FFILE is that multiple copies of the same version of a particular
|
|
// record could end up in cache.
|
|
pRCache->pFile = pFile;
|
|
|
|
flmRcaLinkIntoRCache( pNewerRCache, pOlderRCache,
|
|
pRCache, !bDontPoisonCache);
|
|
|
|
RCA_SET_READING_IN( pRCache->uiFlags);
|
|
RCA_INCR_USE_COUNT( pRCache->uiFlags);
|
|
pRCache->pNotifyList = NULL;
|
|
|
|
// Unlock mutex before reading in from disk.
|
|
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
bRCacheMutexLocked = FALSE;
|
|
|
|
if( pbTransStarted && !bInitializedFdb)
|
|
{
|
|
if ( RC_BAD( rc = fdbInit( pDb, FLM_READ_TRANS,
|
|
FDB_TRANS_GOING_OK, 0, pbTransStarted)))
|
|
{
|
|
fdbExit( pDb);
|
|
goto Notify_Waiters;
|
|
}
|
|
bInitializedFdb = TRUE;
|
|
}
|
|
|
|
// Read record from disk.
|
|
|
|
#ifdef FLM_DBG_LOG
|
|
flmDbgLogWrite( pFile->uiFFileId, uiContainer,
|
|
uiDrn, pDb->LogHdr.uiCurrTransID, "RRD");
|
|
#endif
|
|
|
|
if (pStack)
|
|
{
|
|
rc = FSReadElement( pDb, &pDb->TempPool, pLFile,
|
|
uiDrn, pStack, TRUE, ppRecord,
|
|
&uiLowTransId, &bMostCurrent);
|
|
}
|
|
else if ((pLFile) ||
|
|
(RC_OK( rc = fdictGetContainer( pDb->pDict, uiContainer, &pLFile))))
|
|
{
|
|
rc = FSReadRecord( pDb, pLFile, uiDrn, ppRecord,
|
|
&uiLowTransId, &bMostCurrent);
|
|
}
|
|
|
|
Notify_Waiters:
|
|
|
|
// Relock mutex
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
bRCacheMutexLocked = TRUE;
|
|
|
|
// If read was successful, link the record to its place in
|
|
// the FFILE list and coalesce any versions that overlap
|
|
// this one.
|
|
|
|
if (RC_OK( rc))
|
|
{
|
|
flmRcaLinkToFFILE( pRCache,
|
|
pDb->pFile, pDb, uiLowTransId, bMostCurrent);
|
|
}
|
|
|
|
RCA_UNSET_READING_IN( pRCache->uiFlags);
|
|
|
|
// Notify any threads waiting for the read to complete.
|
|
|
|
pNotify = pRCache->pNotifyList;
|
|
pRCache->pNotifyList = NULL;
|
|
if (pNotify)
|
|
{
|
|
flmRcaNotify( pNotify,
|
|
(RCACHE *)((RC_BAD( rc))
|
|
? NULL
|
|
: pRCache), rc);
|
|
}
|
|
RCA_DECR_USE_COUNT( pRCache->uiFlags);
|
|
|
|
// If we did not succeed, free the RCACHE structure.
|
|
|
|
if (RC_BAD( rc))
|
|
{
|
|
flmRcaFreeCache( pRCache, FALSE);
|
|
goto Exit;
|
|
}
|
|
|
|
// If this item was purged while we were reading it in,
|
|
// start over with the search.
|
|
|
|
if (RCA_IS_PURGED( pRCache->uiFlags))
|
|
{
|
|
if (!RCA_IS_IN_USE( pRCache->uiFlags))
|
|
{
|
|
flmRcaFreePurged( pRCache);
|
|
}
|
|
|
|
// Start over with the find - this one has
|
|
// been marked for purging.
|
|
|
|
pNewRecord = NULL;
|
|
pOldRecord = NULL;
|
|
bGotFromDisk = FALSE;
|
|
goto Start_Find;
|
|
}
|
|
|
|
// When reading from disk, no need to verify that we
|
|
// are getting the app implementation - because we
|
|
// always will.
|
|
|
|
bGotFromDisk = TRUE;
|
|
pRecord = *ppRecord;
|
|
flmRcaSetRecord( pRCache, pRecord);
|
|
|
|
Found_Record:
|
|
|
|
if (!bGotFromDisk)
|
|
{
|
|
if( (pRecord = *ppRecord) != pRCache->pRecord)
|
|
{
|
|
if (*ppRecord)
|
|
{
|
|
FlmRecordExt::Release( *ppRecord, bRCacheMutexLocked);
|
|
}
|
|
|
|
pRecord = *ppRecord = pRCache->pRecord;
|
|
FlmRecordExt::AddRef( pRecord, bRCacheMutexLocked);
|
|
}
|
|
}
|
|
|
|
// Clean up cache, if necessary.
|
|
|
|
if (gv_FlmSysData.RCacheMgr.pRCacheAlloc->getTotalBytesAllocated() >
|
|
gv_FlmSysData.RCacheMgr.Usage.uiMaxBytes)
|
|
{
|
|
flmRcaReduceCache( bRCacheMutexLocked);
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pNewRecord)
|
|
{
|
|
FlmRecordExt::Release( pNewRecord, bRCacheMutexLocked);
|
|
}
|
|
|
|
if (bRCacheMutexLocked)
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
if (bInitializedFdb)
|
|
{
|
|
fdbExit(pDb);
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine inserts a record into the record cache. This is ONLY
|
|
called by FlmRecordModify or FlmRecordAdd. In the case of a modify,
|
|
this should replace any uncommitted version of the record that may
|
|
have been put there by a prior call to FlmRecordModify.
|
|
****************************************************************************/
|
|
RCODE flmRcaInsertRec(
|
|
FDB * pDb,
|
|
LFILE * pLFile, // Container record is in.
|
|
FLMUINT uiDrn, // DRN of record
|
|
FlmRecord * pRecord) // Record to be inserted.
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
FFILE_p pFile = pDb->pFile;
|
|
FLMUINT uiContainer = pLFile->uiLfNum;
|
|
FLMBOOL bMutexLocked = FALSE;
|
|
RCACHE * pRCache;
|
|
RCACHE * pNewerRCache;
|
|
RCACHE * pOlderRCache;
|
|
FLMBOOL bDontPoisonCache = pDb->uiFlags & FDB_DONT_POISON_CACHE
|
|
? TRUE
|
|
: FALSE;
|
|
|
|
if (pLFile->bMakeFieldIdTable && !pRecord->fieldIdTableEnabled())
|
|
{
|
|
|
|
// NOTE: createFieldIdTable will call sortFieldIdTable().
|
|
|
|
if (RC_BAD( rc = pRecord->createFieldIdTable( TRUE)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pRecord->sortFieldIdTable();
|
|
if (getFieldIdTableItemCount( pRecord->getFieldIdTbl()) !=
|
|
getFieldIdTableArraySize( pRecord->getFieldIdTbl()))
|
|
{
|
|
if (RC_BAD( rc = pRecord->truncateFieldIdTable()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
flmAssert( uiDrn != 0);
|
|
|
|
// Lock the mutex
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
bMutexLocked = TRUE;
|
|
|
|
if ((gv_FlmSysData.RCacheMgr.Usage.uiCount >
|
|
FLM_RCA_MAX_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets < MAX_RCACHE_BUCKETS) ||
|
|
(gv_FlmSysData.RCacheMgr.Usage.uiCount <
|
|
FLM_RCA_MIN_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets > MIN_RCACHE_BUCKETS))
|
|
{
|
|
if (RC_BAD( rc = flmRcaRehash()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if we can find the record in cache
|
|
|
|
flmRcaFindRec( uiContainer, uiDrn, pFile,
|
|
pDb->LogHdr.uiCurrTransID, bDontPoisonCache, NULL, &pRCache,
|
|
&pNewerRCache, &pOlderRCache);
|
|
|
|
if (pRCache)
|
|
{
|
|
|
|
// If we found the last committed version, instead of replacing it,
|
|
// we want to change its high transaction ID, and go create a new
|
|
// record to put in cache.
|
|
|
|
if (pRCache->uiLowTransId < pDb->LogHdr.uiCurrTransID)
|
|
{
|
|
|
|
// pOlderRCache and pRCache should be the same at this point if we
|
|
// found something. Furthermore, the high transaction ID on what
|
|
// we found better be -1 - most current version.
|
|
|
|
flmAssert( pOlderRCache == pRCache);
|
|
flmAssert( pOlderRCache->uiHighTransId == 0xFFFFFFFF);
|
|
|
|
flmRcaSetTransID( pOlderRCache, (pDb->LogHdr.uiCurrTransID - 1));
|
|
|
|
flmAssert( pOlderRCache->uiHighTransId >= pOlderRCache->uiLowTransId);
|
|
|
|
RCA_SET_UNCOMMITTED( pOlderRCache->uiFlags);
|
|
RCA_SET_LATEST_VER( pOlderRCache->uiFlags);
|
|
flmRcaUnlinkFromFile( pOlderRCache);
|
|
flmRcaLinkToFileAtHead( pOlderRCache, pFile);
|
|
}
|
|
else
|
|
{
|
|
// Found latest UNCOMMITTED VERSION - replace it.
|
|
|
|
#ifdef FLM_CHECK_RECORD
|
|
if (RC_BAD( rc = pRecord->checkRecord()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
#endif
|
|
|
|
if (RC_BAD( rc = pRecord->compressMemory()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
#ifdef FLM_CHECK_RECORD
|
|
if (RC_BAD( rc = pRecord->checkRecord()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
#endif
|
|
|
|
// Replace the old record data with the new record data.
|
|
|
|
flmRcaSetRecord( pRCache, pRecord);
|
|
|
|
// Make sure we set the "uncommitted" flag and move the record
|
|
// to the head of the FFILE's list.
|
|
|
|
if (!RCA_IS_UNCOMMITTED( pRCache->uiFlags))
|
|
{
|
|
RCA_SET_UNCOMMITTED( pRCache->uiFlags);
|
|
flmRcaUnlinkFromFile( pRCache);
|
|
flmRcaLinkToFileAtHead( pRCache, pFile);
|
|
}
|
|
|
|
// Will not have already been put at MRU if bDonPoisonCache is TRUE.
|
|
|
|
if (pRCache->pPrevInGlobal)
|
|
{
|
|
flmRcaUnlinkFromGlobal( pRCache);
|
|
flmRcaLinkToGlobalAsMRU( pRCache);
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// We are positioned to insert the new record. For an update, it
|
|
// must always be the newest version.
|
|
|
|
flmAssert( pNewerRCache == NULL);
|
|
|
|
#ifdef FLM_CHECK_RECORD
|
|
if (RC_BAD( rc = pRecord->checkRecord()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
#endif
|
|
|
|
if (RC_BAD( rc = pRecord->compressMemory()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
#ifdef FLM_CHECK_RECORD
|
|
if (RC_BAD( rc = pRecord->checkRecord()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
#endif
|
|
|
|
// Allocate a new RCACHE structure.
|
|
|
|
if (RC_BAD( rc = flmRcaAllocCacheStruct( &pRCache)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
// Set the DRN and container for the structure.
|
|
|
|
pRCache->uiDrn = uiDrn;
|
|
pRCache->uiContainer = uiContainer;
|
|
pRCache->pFile = pFile;
|
|
|
|
// Link into the global list and hash bucket.
|
|
|
|
flmRcaLinkIntoRCache( pNewerRCache, pOlderRCache, pRCache, TRUE);
|
|
|
|
// Link to its FFILE and coalesce out duplicates.
|
|
// NOTE: This routine automatically puts an uncommitted version
|
|
// at the head of the FFILE's list and sets the uncommitted flag.
|
|
|
|
flmRcaLinkToFFILE( pRCache, pFile, pDb, pDb->LogHdr.uiCurrTransID, TRUE);
|
|
|
|
// Set the record data pointer for the RCACHE structure. This will also
|
|
// release the old data pointer and set the read only flag and the mutex
|
|
// for the record data. Also updates memory usage fields in RCacheMgr.
|
|
|
|
flmRcaSetRecord( pRCache, pRecord);
|
|
|
|
// Clean up cache, if necessary.
|
|
|
|
flmRcaReduceCache( bMutexLocked);
|
|
|
|
Exit:
|
|
|
|
if (bMutexLocked)
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine is called by FlmRecordDelete to remove a record from
|
|
cache. If there is an uncommitted version of the record, it should
|
|
remove that version from cache. If the last committed version is in
|
|
cache, it should set the high transaction ID on that version to be
|
|
one less than the transaction ID of the update transaction that is
|
|
doing the FlmRecordDelete call.
|
|
****************************************************************************/
|
|
RCODE flmRcaRemoveRec(
|
|
FDB * pDb,
|
|
FLMUINT uiContainer,
|
|
FLMUINT uiDrn)
|
|
{
|
|
RCODE rc = FERR_OK;
|
|
FLMBOOL bMutexLocked = FALSE;
|
|
RCACHE * pRCache;
|
|
RCACHE * pNewerRCache;
|
|
RCACHE * pOlderRCache;
|
|
FFILE_p pFile = pDb->pFile;
|
|
|
|
flmAssert( uiDrn != 0);
|
|
|
|
// Lock the semaphore
|
|
|
|
bMutexLocked = TRUE;
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
|
|
if ((gv_FlmSysData.RCacheMgr.Usage.uiCount >
|
|
FLM_RCA_MAX_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets < MAX_RCACHE_BUCKETS) ||
|
|
(gv_FlmSysData.RCacheMgr.Usage.uiCount <
|
|
FLM_RCA_MIN_REC_CNT( gv_FlmSysData.RCacheMgr.uiNumBuckets) &&
|
|
gv_FlmSysData.RCacheMgr.uiNumBuckets > MIN_RCACHE_BUCKETS))
|
|
{
|
|
if (RC_BAD( rc = flmRcaRehash()))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// See if we can find the record in cache
|
|
|
|
flmRcaFindRec( uiContainer, uiDrn, pFile,
|
|
pDb->LogHdr.uiCurrTransID, FALSE, NULL, &pRCache,
|
|
&pNewerRCache, &pOlderRCache);
|
|
|
|
if (pRCache)
|
|
{
|
|
// FlmRecordDelete is calling this routine, so we determine if we found
|
|
// the last committed version or a record that was added by this same
|
|
// transaction. If we found the last committed version, set its high
|
|
// transaction ID. Otherwise, remove the record from cache.
|
|
|
|
if (pRCache->uiLowTransId < pDb->LogHdr.uiCurrTransID)
|
|
{
|
|
|
|
// pOlderRCache and pRCache should be the same at this point if we
|
|
// found something. Furthermore, the high transaction ID on what
|
|
// we found better be -1 - most current version.
|
|
|
|
flmAssert( pOlderRCache == pRCache);
|
|
flmAssert( pOlderRCache->uiHighTransId == 0xFFFFFFFF);
|
|
|
|
flmRcaSetTransID( pOlderRCache, (pDb->LogHdr.uiCurrTransID - 1));
|
|
flmAssert( pOlderRCache->uiHighTransId >= pOlderRCache->uiLowTransId);
|
|
RCA_SET_UNCOMMITTED( pOlderRCache->uiFlags);
|
|
RCA_SET_LATEST_VER( pOlderRCache->uiFlags);
|
|
flmRcaUnlinkFromFile( pOlderRCache);
|
|
flmRcaLinkToFileAtHead( pOlderRCache, pFile);
|
|
}
|
|
else
|
|
{
|
|
flmRcaFreeCache( pRCache,
|
|
(FLMBOOL)(RCA_IS_IN_USE( pRCache->uiFlags)
|
|
? TRUE
|
|
: FALSE));
|
|
}
|
|
}
|
|
|
|
// Take the opportunity to clean up cache.
|
|
|
|
flmRcaReduceCache( bMutexLocked);
|
|
|
|
Exit:
|
|
|
|
if (bMutexLocked)
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine is called when an FFILE structure is going to be removed
|
|
from the shared memory area. At that point, we also need to get rid
|
|
of all records that have been cached for that FFILE.
|
|
****************************************************************************/
|
|
void flmRcaFreeFileRecs(
|
|
FFILE_p pFile)
|
|
{
|
|
FLMUINT uiNumFreed = 0;
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
while (pFile->pFirstRecord)
|
|
{
|
|
flmRcaFreeCache( pFile->pFirstRecord,
|
|
RCA_IS_IN_USE( pFile->pFirstRecord->uiFlags)
|
|
? TRUE
|
|
: FALSE);
|
|
|
|
// Release the CPU every 100 records freed.
|
|
|
|
if (uiNumFreed < 100)
|
|
{
|
|
uiNumFreed++;
|
|
}
|
|
else
|
|
{
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
f_yieldCPU();
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
uiNumFreed = 0;
|
|
}
|
|
}
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine is called when an update transaction aborts. At that
|
|
point, we need to get rid of any uncommitted versions of records in
|
|
the record cache.
|
|
****************************************************************************/
|
|
void flmRcaAbortTrans(
|
|
FDB * pDb
|
|
)
|
|
{
|
|
FFILE_p pFile = pDb->pFile;
|
|
RCACHE * pRCache;
|
|
RCACHE * pOlderVersion;
|
|
FLMUINT uiOlderTransId =
|
|
pDb->LogHdr.uiCurrTransID - 1;
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
pRCache = pFile->pFirstRecord;
|
|
while (pRCache)
|
|
{
|
|
if (RCA_IS_UNCOMMITTED( pRCache->uiFlags))
|
|
{
|
|
if (RCA_IS_LATEST_VER( pRCache->uiFlags))
|
|
{
|
|
flmRcaSetTransID( pRCache, 0xFFFFFFFF);
|
|
RCA_UNSET_UNCOMMITTED( pRCache->uiFlags);
|
|
RCA_UNSET_LATEST_VER( pRCache->uiFlags);
|
|
flmRcaUnlinkFromFile( pRCache);
|
|
flmRcaLinkToFileAtEnd( pRCache, pFile);
|
|
}
|
|
else
|
|
{
|
|
// Save the older version - we may be changing its
|
|
// high transaction ID back to 0xFFFFFFFF
|
|
|
|
pOlderVersion = pRCache->pOlderVersion;
|
|
|
|
// Free the uncommitted version.
|
|
|
|
flmRcaFreeCache( pRCache,
|
|
(FLMBOOL)((RCA_IS_IN_USE( pRCache->uiFlags) ||
|
|
RCA_IS_READING_IN( pRCache->uiFlags))
|
|
? TRUE
|
|
: FALSE));
|
|
|
|
// If the older version has a high transaction ID that
|
|
// is exactly one less than our current transaction,
|
|
// it is the most current version. Hence, we need to
|
|
// change its high transaction ID back to 0xFFFFFFFF.
|
|
|
|
if ((pOlderVersion) &&
|
|
(pOlderVersion->uiHighTransId == uiOlderTransId))
|
|
{
|
|
flmRcaSetTransID( pOlderVersion, 0xFFFFFFFF);
|
|
}
|
|
}
|
|
pRCache = pFile->pFirstRecord;
|
|
}
|
|
else
|
|
{
|
|
// We can stop when we hit a committed version, because
|
|
// uncommitted versions are always linked in together at
|
|
// the head of the list.
|
|
|
|
break;
|
|
}
|
|
}
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine is called when an update transaction commits. At that
|
|
point, we need to unset the "uncommitted" flag on any records
|
|
currently in record cache for the FFILE.
|
|
****************************************************************************/
|
|
void flmRcaCommitTrans(
|
|
FDB * pDb)
|
|
{
|
|
RCACHE * pRCache;
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
pRCache = pDb->pFile->pFirstRecord;
|
|
while (pRCache)
|
|
{
|
|
if (RCA_IS_UNCOMMITTED( pRCache->uiFlags))
|
|
{
|
|
RCA_UNSET_UNCOMMITTED( pRCache->uiFlags);
|
|
RCA_UNSET_LATEST_VER( pRCache->uiFlags);
|
|
pRCache = pRCache->pNextInFile;
|
|
}
|
|
else
|
|
{
|
|
|
|
// We can stop when we hit a committed version, because
|
|
// uncommitted versions are always linked in together at
|
|
// the head of the list.
|
|
|
|
break;
|
|
}
|
|
}
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: This routine is called when a container in the database is deleted.
|
|
All records in record cache that are in that container must be
|
|
removed from cache.
|
|
****************************************************************************/
|
|
void flmRcaRemoveContainerRecs(
|
|
FDB * pDb,
|
|
FLMUINT uiContainer)
|
|
{
|
|
FFILE * pFile = pDb->pFile;
|
|
RCACHE * pRCache;
|
|
RCACHE * pPrevRCache;
|
|
FLMUINT uiTransId = pDb->LogHdr.uiCurrTransID;
|
|
|
|
f_mutexLock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
pRCache = gv_FlmSysData.RCacheMgr.pLRURecord;
|
|
|
|
// Stay in the loop until we have freed all old records, or
|
|
// we have run through the entire list.
|
|
|
|
while (pRCache)
|
|
{
|
|
// Save the pointer to the previous entry in the list because
|
|
// we may end up unlinking pRCache below, in which case we would
|
|
// have lost the previous entry.
|
|
|
|
pPrevRCache = pRCache->pPrevInGlobal;
|
|
|
|
// Only look at records in this container and this database.
|
|
|
|
if ((pRCache->uiContainer == uiContainer) &&
|
|
(pRCache->pFile == pFile))
|
|
{
|
|
|
|
// Only look at the most current versions.
|
|
|
|
if (pRCache->uiHighTransId == 0xFFFFFFFF)
|
|
{
|
|
|
|
// Better not be a newer version.
|
|
|
|
flmAssert( pRCache->pNewerVersion == NULL);
|
|
|
|
if (pRCache->uiLowTransId < uiTransId)
|
|
{
|
|
|
|
// This version was not added or modified by this
|
|
// transaction so it's high transaction ID should simply
|
|
// be set to one less than the current transaction ID.
|
|
|
|
flmRcaSetTransID( pRCache, uiTransId - 1);
|
|
flmAssert( pRCache->uiHighTransId >= pRCache->uiLowTransId);
|
|
RCA_SET_UNCOMMITTED( pRCache->uiFlags);
|
|
RCA_SET_LATEST_VER( pRCache->uiFlags);
|
|
flmRcaUnlinkFromFile( pRCache);
|
|
flmRcaLinkToFileAtHead( pRCache, pFile);
|
|
}
|
|
else
|
|
{
|
|
|
|
// The record was added or modified in this
|
|
// transaction. Simply remove it from cache.
|
|
|
|
flmRcaFreeCache( pRCache,
|
|
(FLMBOOL)(RCA_IS_IN_USE( pRCache->uiFlags)
|
|
? TRUE
|
|
: FALSE));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// If not most current version, the record's high transaction
|
|
// ID better already be less than transaction ID.
|
|
|
|
flmAssert( pRCache->uiHighTransId < uiTransId);
|
|
}
|
|
}
|
|
pRCache = pPrevRCache;
|
|
|
|
}
|
|
f_mutexUnlock( gv_FlmSysData.RCacheMgr.hMutex);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
****************************************************************************/
|
|
#ifdef FLM_DEBUG
|
|
FSTATIC RCODE flmRcaCheck(
|
|
FDB_p pDb,
|
|
FLMUINT uiContainer,
|
|
FLMUINT uiDrn)
|
|
{
|
|
LFILE * pLFile;
|
|
FlmRecord * pRecord = NULL;
|
|
FLMUINT uiLowTransId;
|
|
FLMBOOL bMostCurrent;
|
|
RCODE rc;
|
|
|
|
if( RC_OK( rc = fdictGetContainer( pDb->pDict, uiContainer, &pLFile)))
|
|
{
|
|
rc = FSReadRecord( pDb, pLFile, uiDrn, &pRecord,
|
|
&uiLowTransId, &bMostCurrent);
|
|
}
|
|
|
|
if( pRecord)
|
|
{
|
|
pRecord->Release();
|
|
}
|
|
|
|
return( rc);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
****************************************************************************/
|
|
FLMBOOL FlmRecordExt::canRelocateRec(
|
|
void * pvAlloc)
|
|
{
|
|
FlmRecord * pRec = (FlmRecord *)pvAlloc;
|
|
|
|
if( pRec->getRefCount() == 1 && pRec->isCached())
|
|
{
|
|
return( TRUE);
|
|
}
|
|
|
|
return( FALSE);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
****************************************************************************/
|
|
void FlmRecordExt::relocateRec(
|
|
void * pvOldAlloc,
|
|
void * pvNewAlloc)
|
|
{
|
|
FlmRecord * pOldRec = (FlmRecord *)pvOldAlloc;
|
|
FlmRecord * pNewRec = (FlmRecord *)pvNewAlloc;
|
|
RCACHE * pRCache;
|
|
RCACHE * pVersion;
|
|
|
|
flmAssert( pOldRec->getRefCount() == 1);
|
|
flmAssert( pOldRec->isCached());
|
|
flmAssert( pvNewAlloc < pvOldAlloc);
|
|
|
|
// Update the record pointer in the data buffer
|
|
|
|
if( pNewRec->m_pucBuffer)
|
|
{
|
|
flmAssert( *((FlmRecord **)pOldRec->m_pucBuffer) == pOldRec);
|
|
*((FlmRecord **)pNewRec->m_pucBuffer) = pNewRec;
|
|
}
|
|
if( pNewRec->m_pucFieldIdTable)
|
|
{
|
|
flmAssert( *((FlmRecord **)pOldRec->m_pucFieldIdTable) == pOldRec);
|
|
*((FlmRecord **)pNewRec->m_pucFieldIdTable) = pNewRec;
|
|
}
|
|
|
|
// Find the record
|
|
|
|
pRCache = *(FLM_RCA_HASH( pNewRec->m_uiRecordID));
|
|
|
|
while( pRCache)
|
|
{
|
|
if( pRCache->uiDrn == pNewRec->m_uiRecordID)
|
|
{
|
|
pVersion = pRCache;
|
|
|
|
while( pVersion)
|
|
{
|
|
if( pVersion->pRecord == pOldRec)
|
|
{
|
|
pVersion->pRecord = pNewRec;
|
|
goto Done;
|
|
}
|
|
|
|
pVersion = pVersion->pOlderVersion;
|
|
}
|
|
}
|
|
|
|
pRCache = pRCache->pNextInBucket;
|
|
}
|
|
|
|
Done:
|
|
|
|
flmAssert( pRCache);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
****************************************************************************/
|
|
FLMBOOL FlmRecordExt::canRelocateRecBuffer(
|
|
void * pvAlloc)
|
|
{
|
|
FlmRecord * pRec = *((FlmRecord **)pvAlloc);
|
|
|
|
flmAssert( pRec->m_pucBuffer == pvAlloc || pRec->m_pucFieldIdTable == pvAlloc);
|
|
|
|
if( pRec->getRefCount() == 1 && pRec->isCached())
|
|
{
|
|
return( TRUE);
|
|
}
|
|
|
|
return( FALSE);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
****************************************************************************/
|
|
void FlmRecordExt::relocateRecBuffer(
|
|
void * pvOldAlloc,
|
|
void * pvNewAlloc)
|
|
{
|
|
FlmRecord * pRec = *((FlmRecord **)pvOldAlloc);
|
|
|
|
flmAssert( pRec->getRefCount() == 1);
|
|
flmAssert( pRec->isCached());
|
|
flmAssert( pvNewAlloc < pvOldAlloc);
|
|
|
|
// Update the buffer pointer in the record
|
|
|
|
if (pRec->m_pucBuffer == pvOldAlloc)
|
|
{
|
|
pRec->m_pucBuffer = (FLMBYTE *)pvNewAlloc;
|
|
}
|
|
else if (pRec->m_pucFieldIdTable == pvOldAlloc)
|
|
{
|
|
pRec->m_pucFieldIdTable = (FLMBYTE *)pvNewAlloc;
|
|
}
|
|
else
|
|
{
|
|
flmAssert( 0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc:
|
|
Notes: This routine assumes the rcache mutex is locked
|
|
****************************************************************************/
|
|
FSTATIC FLMBOOL rcaCanRelocate(
|
|
void * pvAlloc)
|
|
{
|
|
RCACHE * pRCache = (RCACHE *)pvAlloc;
|
|
|
|
if( RCA_IS_IN_USE( pRCache->uiFlags) ||
|
|
RCA_IS_READING_IN( pRCache->uiFlags))
|
|
{
|
|
return( FALSE);
|
|
}
|
|
|
|
return( TRUE);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Desc: Fixes up all pointers needed to allow an RCACHE struct to be
|
|
moved to a different location in memory
|
|
Notes: This routine assumes the rcache mutex is locked
|
|
****************************************************************************/
|
|
FSTATIC void rcaRelocate(
|
|
void * pvOldAlloc,
|
|
void * pvNewAlloc)
|
|
{
|
|
RCACHE * pOldRCache = (RCACHE *)pvOldAlloc;
|
|
RCACHE * pNewRCache = (RCACHE *)pvNewAlloc;
|
|
RCACHE ** ppBucket;
|
|
RCACHE_MGR * pRCacheMgr = &gv_FlmSysData.RCacheMgr;
|
|
FFILE * pFile = pOldRCache->pFile;
|
|
|
|
flmAssert( !RCA_IS_IN_USE( pOldRCache->uiFlags));
|
|
flmAssert( !RCA_IS_READING_IN( pOldRCache->uiFlags));
|
|
flmAssert( !pOldRCache->pNotifyList);
|
|
|
|
if( pNewRCache->pPrevInFile)
|
|
{
|
|
pNewRCache->pPrevInFile->pNextInFile = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pNextInFile)
|
|
{
|
|
pNewRCache->pNextInFile->pPrevInFile = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pPrevInGlobal)
|
|
{
|
|
pNewRCache->pPrevInGlobal->pNextInGlobal = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pNextInGlobal)
|
|
{
|
|
pNewRCache->pNextInGlobal->pPrevInGlobal = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pPrevInBucket)
|
|
{
|
|
pNewRCache->pPrevInBucket->pNextInBucket = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pNextInBucket)
|
|
{
|
|
pNewRCache->pNextInBucket->pPrevInBucket = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pOlderVersion)
|
|
{
|
|
pNewRCache->pOlderVersion->pNewerVersion = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pNewerVersion)
|
|
{
|
|
pNewRCache->pNewerVersion->pOlderVersion = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pPrevInHeapList)
|
|
{
|
|
pNewRCache->pPrevInHeapList->pNextInHeapList = pNewRCache;
|
|
}
|
|
|
|
if( pNewRCache->pNextInHeapList)
|
|
{
|
|
pNewRCache->pNextInHeapList->pPrevInHeapList = pNewRCache;
|
|
}
|
|
|
|
ppBucket = FLM_RCA_HASH( pOldRCache->uiDrn);
|
|
if( *ppBucket == pOldRCache)
|
|
{
|
|
*ppBucket = pNewRCache;
|
|
}
|
|
|
|
if( pRCacheMgr->pHeapList == pOldRCache)
|
|
{
|
|
pRCacheMgr->pHeapList = pNewRCache;
|
|
}
|
|
|
|
if( pRCacheMgr->pPurgeList == pOldRCache)
|
|
{
|
|
pRCacheMgr->pPurgeList = pNewRCache;
|
|
}
|
|
|
|
if( pRCacheMgr->pMRURecord == pOldRCache)
|
|
{
|
|
pRCacheMgr->pMRURecord = pNewRCache;
|
|
}
|
|
|
|
if( pRCacheMgr->pLRURecord == pOldRCache)
|
|
{
|
|
pRCacheMgr->pLRURecord = pNewRCache;
|
|
}
|
|
|
|
if( pFile)
|
|
{
|
|
if( pFile->pFirstRecord == pOldRCache)
|
|
{
|
|
pFile->pFirstRecord = pNewRCache;
|
|
}
|
|
|
|
if( pFile->pLastRecord == pOldRCache)
|
|
{
|
|
pFile->pLastRecord = pNewRCache;
|
|
}
|
|
}
|
|
|
|
#ifdef FLM_DEBUG
|
|
f_memset( pOldRCache, 0, sizeof( RCACHE));
|
|
#endif
|
|
}
|