windows-nt/Source/XPSP1/NT/inetsrv/query/ntciutil/phystr.cxx
2020-09-26 16:20:57 +08:00

1612 lines
46 KiB
C++

//+---------------------------------------------------------------------------
//
// Microsoft Corporation
// Copyright (C) Microsoft Corporation, 1992 - 1998.
//
// File: PhysIdx.CXX
//
// Contents: FAT Buffer/Index package
//
// Classes: CPhysBuffer -- Buffer
//
// History: 05-Mar-92 KyleP Created
// 07-Aug-92 KyleP Kernel implementation
// 21-Apr-93 BartoszM Rewrote to use memory mapped files
//
//----------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <pstore.hxx>
#include <phystr.hxx>
#include <eventlog.hxx>
#include <psavtrak.hxx>
//+-------------------------------------------------------------------------
//
// Function: PageToLow
//
// Returns: Low part of byte count in cPage pages
//
// History: 21-Apr-93 BartoszM Created
//
//--------------------------------------------------------------------------
inline ULONG PageToLow(ULONG cPage)
{
return(cPage << CI_PAGE_SHIFT);
}
//+-------------------------------------------------------------------------
//
// Function: PageToHigh
//
// Returns: High part of byte count in cPage pages
//
// History: 21-Apr-93 BartoszM Created
//
//--------------------------------------------------------------------------
inline ULONG PageToHigh(ULONG cPage)
{
return(cPage >> (ULONG_BITS - CI_PAGE_SHIFT));
}
//+-------------------------------------------------------------------------
//
// Function: ToPages
//
// Returns: Number of pages equivalent to (cbLow, cbHigh) bytes
//
// History: 21-Apr-93 BartoszM Created
//
//--------------------------------------------------------------------------
inline ULONG ToPages ( ULONG cbLow, ULONG cbHigh )
{
Win4Assert ( cbHigh >> CI_PAGE_SHIFT == 0 );
return (cbLow >> CI_PAGE_SHIFT) + (cbHigh << (ULONG_BITS - CI_PAGE_SHIFT));
}
//+-------------------------------------------------------------------------
//
// Function: PgCommonPgTrunc
//
// Returns: Number of pages truncated to multiple of largest common page
//
// History: 21-Apr-93 BartoszM Created
//
//--------------------------------------------------------------------------
inline ULONG PgCommonPgTrunc(ULONG nPage)
{
return nPage & ~(PAGES_PER_COMMON_PAGE - 1);
}
inline ULONGLONG MAKEULONGLONG( ULONG l, ULONG h )
{
return ( l | ( h << 32 ) );
}
//+-------------------------------------------------------------------------
//
// Function: PgCommonPgRound
//
// Returns: Number of pages rounded to multiple of largest common page
//
// History: 21-Apr-93 BartoszM Created
//
//--------------------------------------------------------------------------
inline ULONG PgCommonPgRound(ULONG nPage)
{
return (nPage + PAGES_PER_COMMON_PAGE - 1) & ~(PAGES_PER_COMMON_PAGE - 1);
}
//+-------------------------------------------------------------------------
//
// Member: CPhysBuffer::~CPhysBuffer
//
// History: 07-Dec-93 BartoszM Created
//
//--------------------------------------------------------------------------
CPhysBuffer::~CPhysBuffer()
{
Win4Assert( !_fNeedToFlush );
#if CIDBG == 1
Flush( FALSE );
#endif
}
//+-------------------------------------------------------------------------
//
// Member: CPhysBuffer::CPhysBuffer, public
//
// Synopsis: Allocates virtual memory
//
// Arguments: [stream] -- stream to use
// [PageNo] -- page number
// [fWrite] -- for writable memory
// [fIntentToWrite] -- When fWrite is TRUE, whether the
// client intends to write to the memory so
// it needs to be flushed.
//
// History: 10-Mar-92 KyleP Created
// 21-Apr-93 BartoszM Rewrote to use memory mapped files
//
//--------------------------------------------------------------------------
CPhysBuffer::CPhysBuffer(
PMmStream& stream,
ULONG PageNo,
BOOL fWrite,
BOOL fIntentToWrite,
BOOL fMap ) :
_cRef( 0 ),
_pphysNext( 0 ),
_fNeedToFlush( fIntentToWrite ),
_fWrite( fWrite ),
_fMap( fMap ),
_stream( stream )
{
_PageNo = PgCommonPgTrunc( PageNo );
if ( _fMap )
stream.Map( _strmBuf,
COMMON_PAGE_SIZE,
PageToLow( _PageNo ),
PageToHigh( _PageNo ),
fWrite );
else
{
DWORD cbRead;
_xBuffer.Init( COMMON_PAGE_SIZE );
_stream.Read( _xBuffer.Get(),
MAKEULONGLONG( PageToLow( _PageNo ),
PageToHigh( _PageNo ) ),
COMMON_PAGE_SIZE,
cbRead );
}
} //CPhysBuffer
//+-------------------------------------------------------------------------
//
// Member: CPhysBuffer::Flush, public
//
// Synopsis: Flushes the buffer to disk
//
// Arguments: [fFailFlush] -- If TRUE and flush fails, an exception is
// raised. Otherwise failures to flush are
// ignored.
//
// History: 10/30/98 dlee Moved out of header
//
//--------------------------------------------------------------------------
void CPhysBuffer::Flush( BOOL fFailFlush )
{
if ( _fNeedToFlush )
{
Win4Assert( _fWrite );
//
// Reset the flag first, so subsequent attempts to flush won't
// fail if first attempt fails. This is so we don't throw
// in destructors. If we can't tolerate failures to flush, don't
// even bother trying to write the data if we can't fail.
//
_fNeedToFlush = FALSE;
if ( fFailFlush )
{
if ( _fMap )
{
// Flush the buffer and metadata in one call
_strmBuf.Flush( TRUE );
}
else
{
_stream.Write( _xBuffer.Get(),
MAKEULONGLONG( PageToLow( _PageNo ),
PageToHigh( _PageNo ) ),
COMMON_PAGE_SIZE );
}
}
else
{
ciDebugOut(( DEB_WARN, "not flushing at %#p, in failure path?\n",
this ));
}
}
#if CIDBG == 1
//
// Make sure the bits on disk are the same as those in memory, since
// the buffer is marked as not needing to be flushed. Only do this if
// using read/write instead of mapping.
//
else if ( !_fMap && fFailFlush )
{
TRY
{
XArrayVirtual<BYTE> xTmp( COMMON_PAGE_SIZE );
DWORD cbRead;
_stream.Read( xTmp.Get(),
MAKEULONGLONG( PageToLow( _PageNo ),
PageToHigh( _PageNo ) ),
COMMON_PAGE_SIZE,
cbRead );
if ( 0 != memcmp( xTmp.Get(), _xBuffer.Get(), COMMON_PAGE_SIZE ) )
{
ciDebugOut(( DEB_ERROR, "read: %p, have: %p\n",
xTmp.Get(), _xBuffer.Get() ));
Win4Assert( !"writable non-flushed buffer was modified!" );
}
}
CATCH( CException, e )
{
// Ignore -- it's just an assert.
}
END_CATCH;
}
#endif // CIDBG == 1
} //Flush
//+-------------------------------------------------------------------------
//
// Member: CBufferCacheIter::CBufferCacheIter, public
//
// Synopsis: Constructor
//
// History: 18-Mar-93 BartoszM Created
//
//--------------------------------------------------------------------------
CBufferCacheIter::CBufferCacheIter ( CBufferCache& cache )
: _pPhysBuf(cache._aPhysBuf.GetPointer()), _idx(0), _cHash( cache._cHash )
{
do
{
_pBuf = _pPhysBuf[_idx];
if (_pBuf != 0)
break;
_idx++;
} while ( _idx < _cHash );
} //CBufferCacheIter
//+-------------------------------------------------------------------------
//
// Member: CBufferCacheIter::operator++, public
//
// Synopsis: Increments the iterator
//
// History: 18-Mar-93 BartoszM Created
//
//--------------------------------------------------------------------------
void CBufferCacheIter::operator++()
{
Win4Assert(_pBuf != 0);
_pBuf = _pBuf->Next();
while (_pBuf == 0 && ++_idx < _cHash )
_pBuf = _pPhysBuf[_idx];
}
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::CBufferCache, public
//
// Synopsis: Constructor
//
// History: 18-Mar-93 BartoszM Created
//
//--------------------------------------------------------------------------
CBufferCache::CBufferCache( unsigned cHash )
: _cBuffers( 0 ),
_cHash( cHash ),
_aPhysBuf( cHash )
#if CIDBG == 1
, _cCacheHits( 0 ), _cCacheMisses( 0 )
#endif
{
RtlZeroMemory( _aPhysBuf.GetPointer(), _aPhysBuf.SizeOf() );
}
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::~CBufferCache, public
//
// Synopsis: Destructor
//
// History: 18-Mar-93 BartoszM Created
//
//--------------------------------------------------------------------------
CBufferCache::~CBufferCache()
{
Free( FALSE );
}
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::Free, public
//
// Synopsis: Deletes all buffers
//
// Arguments: [fFailFlush] -- If TRUE, this method will THROW if the flush
// fails. If FALSE, flush failures are
// ignored.
//
// History: 18-Mar-93 BartoszM Created
//
//--------------------------------------------------------------------------
void CBufferCache::Free( BOOL fFailFlush )
{
if ( 0 != _cBuffers )
{
for ( unsigned i = 0; i < _cHash; i++ )
{
// Keep the cache consistent in case we fail flushing.
while ( 0 != _aPhysBuf[i] )
{
CPhysBuffer * pTemp = _aPhysBuf[i];
pTemp->Flush( fFailFlush );
#if CIDBG == 1
if ( pTemp->IsReferenced() )
ciDebugOut(( DEB_WARN,
"Buffer for page %ld still referenced.\n",
pTemp->PageNumber() ));
#endif // CIDBG == 1
_aPhysBuf[i] = pTemp->Next();
delete pTemp;
_cBuffers--;
}
Win4Assert( 0 == _aPhysBuf[i] );
}
Win4Assert( 0 == _cBuffers );
}
} //Free
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::TryToRemoveBuffers, public
//
// Synopsis: Tries to remove unreferenced buffers from the cache.
// If one is not found, it's not a problem.
//
// Arguments: [cMax] -- Recommended maximum in the cache
//
// History: 15-March-96 dlee Created
//
//--------------------------------------------------------------------------
void CBufferCache::TryToRemoveBuffers( unsigned cMax )
{
for ( unsigned i = 0; i < 3 && ( Count() > cMax ); i++ )
{
// find an unreferenced page with the lowest (oldest) usn.
ULONG ulPage = ULONG_MAX;
ULONG ulUSN = ULONG_MAX;
for ( CBufferCacheIter iter(*this); !iter.AtEnd(); ++iter )
{
CPhysBuffer * pPhys = iter.Get();
Win4Assert( 0 != pPhys );
if ( ( ulUSN > pPhys->GetUSN() ) &&
( !pPhys->IsReferenced() ) )
{
ulUSN = pPhys->GetUSN();
ulPage = pPhys->PageNumber();
}
}
if ( ULONG_MAX == ulPage )
break;
else
Destroy( ulPage );
}
} //TryToRemoveBuffers
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::Search, public
//
// Synopsis: Searches the buffer hash for the requested buffer.
//
// Arguments: [ulPage] -- Page number of page to be found.
//
// Returns: 0 if page not currently buffered, or the pointer
// to the (physical) buffer.
//
// History: 09-Mar-92 KyleP Created
// 17-Mar-93 BartoszM Rewrote
//
//--------------------------------------------------------------------------
CPhysBuffer * CBufferCache::Search(
ULONG hash,
ULONG commonPage )
{
// ciDebugOut (( DEB_ITRACE, "CBufferCache::Search %d\n", ulPage ));
for ( CPhysBuffer *pPhys = _aPhysBuf[ hash ];
0 != pPhys;
pPhys = pPhys->Next() )
{
if ( pPhys->PageNumber() == commonPage )
break;
}
#if DBG==1 || CIDBG==1
if ( 0 == pPhys )
_cCacheMisses++;
else
_cCacheHits++;
#endif // DBG==1 || CIDBG==1
return pPhys;
} //Search
//+---------------------------------------------------------------------------
//
// Function: Flush
//
// Synopsis: Flushes all cached pages upto and including the specified
// page to disk.
//
// Arguments: [nHighPage] -- The "CI" page number below which all pages
// must be flushed to disk.
//
// History: 4-20-94 srikants Created
//
// Notes: nHighPage is in units of "CI" page (4K bytes) and not
// CommonPage which is 64K.
//
//----------------------------------------------------------------------------
void CBufferCache::Flush( ULONG nHighPage, BOOL fLeaveFlushFlagAlone )
{
nHighPage = PgCommonPgTrunc(nHighPage);
ciDebugOut(( DEB_ITRACE, "CBufferCache : Flushing All Pages <= 0x%x\n",
nHighPage ));
if ( 0 == _cBuffers )
return;
//
// We Must Flush All pages <= the given page number.
//
for ( CBufferCacheIter iter(*this); !iter.AtEnd(); ++iter )
{
CPhysBuffer * pPhys = iter.Get();
Win4Assert( 0 != pPhys );
if ( pPhys->PageNumber() <= nHighPage )
{
// Call flush even if not required to get assertions checked
BOOL fRequiresFlush = pPhys->RequiresFlush();
pPhys->Flush( TRUE );
if ( fLeaveFlushFlagAlone && fRequiresFlush )
pPhys->SetIntentToWrite();
}
}
} //Flush
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::Destroy, public
//
// Synopsis: Deletes a buffer.
//
// Arguments: [ulPage] -- page number to be deleted
//
// History: 18-Mar-93 BartoszM Created
//
//--------------------------------------------------------------------------
void CBufferCache::Destroy( ULONG ulPage, BOOL fFailFlush )
{
ulPage = PgCommonPgTrunc(ulPage);
ULONG iHash = hash( ulPage );
CPhysBuffer * pPhys = _aPhysBuf[ iHash ];
Win4Assert( 0 != pPhys );
// Find the page. Don't remove from the list until it is flushed.
// Is it first? (common case)
if (pPhys->PageNumber() == ulPage)
{
// This should be flushed before being unmapped. Fix for bug 132655
pPhys->Flush( fFailFlush );
_aPhysBuf[iHash] = pPhys->Next();
}
else
{
// Search linked list
CPhysBuffer * pPhysPrev;
do
{
pPhysPrev = pPhys;
pPhys = pPhys->Next();
Win4Assert( 0 != pPhys );
}
while (pPhys->PageNumber() != ulPage );
// This should be flushed before being unmapped. Fix for bug 132655
pPhys->Flush( fFailFlush );
// delete from the linked list
pPhysPrev->Link( pPhys->Next() );
Win4Assert ( !pPhys->IsReferenced() );
}
_cBuffers--;
delete pPhys;
} //Destroy
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::Add, public
//
// Synopsis: Adds a buffer to the buffer hash.
//
// Arguments: [pPhysBuf] -- Buffer to add.
//
// History: 09-Mar-92 KyleP Created
//
//--------------------------------------------------------------------------
void CBufferCache::Add( CPhysBuffer * pPhysBuf, ULONG hash )
{
_cBuffers++;
pPhysBuf->Reference();
pPhysBuf->Link( _aPhysBuf[ hash ] );
_aPhysBuf[ hash ] = pPhysBuf;
} //Add
//+-------------------------------------------------------------------------
//
// Member: CBufferCache::MinPageInUse, public
//
// Synopsis: Finds the smallest page in use within the cache
//
// Returns : TRUE if any page is in the cache; FALSE o/w
// If TRUE is returned, then minPageInUse will contain the
// minimum page that is present in the cache.
//
// Arguments: minPageInUse (out) Will be set to the minimum page in
// the cache if the return value is TRUE.
//
// History: 02-May-94 DwightKr Created
// 08-Dec-94 SrikantS Fixed to return the correct value for
// empty cache.
//
//--------------------------------------------------------------------------
BOOL CBufferCache::MinPageInUse(ULONG &minPageInUse)
{
ULONG minPage = 0xFFFFFFFF;
for (CBufferCacheIter iter(*this); !iter.AtEnd(); ++iter)
{
minPage = min(minPage, iter.Get()->PageNumber() );
}
if ( minPage == 0xFFFFFFFF )
{
return FALSE;
}
else
{
minPageInUse = minPage;
return TRUE;
}
} //MinPageInUse
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::CPhysStorage, public
//
// Effects: Creates a new index.
//
// Arguments: [storage] -- physical storage to create index in
// [obj] -- Storage object
// [objectId] -- Index ID
// [cPageReqSize] --
// [stream] -- Memory mapped stream
// [fThrowCorruptErrorOnFailures -- If true, then errors during
// opening will be marked as
// catalog corruption
// [cCachedItems] -- # of non-referenced pages to cache
// [fAllowReadAhead] -- if TRUE, non-mapped IO will be used
// when the physical storage says it's ok.
//
// Signals: Cannot create.
//
// Modifies: A new index (file) is created on the disk.
//
// History: 07-Mar-92 KyleP Created
//
//--------------------------------------------------------------------------
CPhysStorage::CPhysStorage( PStorage & storage,
PStorageObject& obj,
WORKID objectId,
UINT cpageReqSize,
PMmStream * stream,
BOOL fThrowCorruptErrorOnFailures,
unsigned cCachedItems,
BOOL fAllowReadAhead )
: _sigPhysStorage(eSigPhysStorage),
_fWritable( TRUE ),
_objectId( objectId ),
_obj ( obj),
_cpageUsedFileSize( 0 ),
_storage(storage),
_stream(stream),
_iFirstNonShrunkPage( 0 ),
_fThrowCorruptErrorOnFailures(fThrowCorruptErrorOnFailures),
_cpageGrowth(1),
_cMaxCachedBuffers( cCachedItems ),
_cache( __max( 17, ( cCachedItems & 1 ) ? cCachedItems :
cCachedItems + 1 ) ),
_usnGen( 0 ),
_fAllowReadAhead( fAllowReadAhead )
{
//
// Use different locking schemes depending on usage
//
if ( 0 == _cMaxCachedBuffers )
_xMutex.Set( new CMutexSem() );
else
_xRWAccess.Set( new CReadWriteAccess() );
if ( _stream.IsNull() || !_stream->Ok() )
{
ciDebugOut((DEB_ERROR, "Index Creation failed %08x\n", objectId ));
if ( _fThrowCorruptErrorOnFailures || _stream->FStatusFileNotFound() )
{
//
// We don't have code to handle such failures, hence mark
// catalog as corrupt; otherwise throw e_fail
//
Win4Assert( !"Corrupt catalog" );
_storage.ReportCorruptComponent( L"PhysStorage1" );
THROW( CException( CI_CORRUPT_DATABASE ));
}
else
THROW( CException( E_FAIL ));
}
if (cpageReqSize == 0)
cpageReqSize = 1;
TRY
{
_GrowFile( cpageReqSize );
}
CATCH( CException, e )
{
//
// There may not be enough space on the disk to allocate the
// requested size. If not enough space, do the best that we
// can and hope space gets freed.
//
_GrowFile( 1 );
}
END_CATCH
ciDebugOut(( DEB_ITRACE, "Physical index %08x created.\n", _objectId ));
}
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::CPhysStorage, public
//
// Effects: Opens an existing index.
//
// Arguments: [storage] -- physical storage to open index in
// [obj] -- Storage object
// [objectId] -- Index ID.
// [stream] -- Memory mapped stream
// [mode] -- Open mode
// [fThrowCorruptErrorOnFailures -- If true, then errors during
// opening will be marked as
// catalog corruption
// [cCachedItems] -- # of non-referenced pages to cache
// [fAllowReadAhead] -- if TRUE, non-mapped IO will be used
// when the physical storage says it's ok.
//
// History: 07-Mar-92 KyleP Created
//
//--------------------------------------------------------------------------
CPhysStorage::CPhysStorage( PStorage & storage,
PStorageObject& obj,
WORKID objectId,
PMmStream * stream,
PStorage::EOpenMode mode,
BOOL fThrowCorruptErrorOnFailures,
unsigned cCachedItems,
BOOL fAllowReadAhead )
: _sigPhysStorage(eSigPhysStorage),
_fWritable( PStorage::eOpenForWrite == mode ),
_objectId( objectId ),
_obj ( obj ),
_cpageUsedFileSize( 0 ),
_storage(storage),
_stream(stream),
_fThrowCorruptErrorOnFailures(fThrowCorruptErrorOnFailures),
_cpageGrowth(1),
_iFirstNonShrunkPage( 0 ),
_cMaxCachedBuffers( cCachedItems ),
_cache( __max( 17, ( cCachedItems & 1 ) ? cCachedItems :
cCachedItems + 1 ) ),
_usnGen( 0 ),
_fAllowReadAhead( fAllowReadAhead )
{
//
// Use different locking schemes depending on usage
//
if ( 0 == _cMaxCachedBuffers )
_xMutex.Set( new CMutexSem() );
else
_xRWAccess.Set( new CReadWriteAccess() );
if ( _stream.IsNull() || !_stream->Ok() )
{
ciDebugOut(( DEB_ERROR, "Open of index %08x failed\n", objectId ));
if ( _fThrowCorruptErrorOnFailures || _stream->FStatusFileNotFound() )
{
//
// We don't have code to handle such failures, hence mark
// catalog as corrupt; otherwise throw e_fail
//
Win4Assert( !"Corrupt catalog" );
_storage.ReportCorruptComponent( L"PhysStorage2" );
THROW( CException( CI_CORRUPT_DATABASE ));
}
else
THROW( CException( E_FAIL ));
}
_cpageFileSize = ToPages(_stream->SizeLow(), _stream->SizeHigh());
ciDebugOut(( DEB_ITRACE, "Physical index %08x (%d pages) opened.\n",
_objectId, _cpageFileSize ));
}
//+---------------------------------------------------------------------------
//
// Member: CPhysStorage::MakeBackupCopy
//
// Synopsis: Makes a backup copy of the current storage using the
// destination storage.
//
// Arguments: [dst] - Destination storage.
// [progressTracker] - Progress tracking
//
// History: 3-18-97 srikants Created
//
//----------------------------------------------------------------------------
void CPhysStorage::MakeBackupCopy( CPhysStorage & dst,
PSaveProgressTracker & progressTracker )
{
//
// Copy one page at a time.
//
ULONG * pSrcNext = 0;
ULONG * pDstNext = 0;
unsigned iCurrBorrow = 0; // always has the current buffers borrowed.
SCODE sc = S_OK;
TRY
{
//
// Grow the destination to be as big as the current one.
//
dst._GrowFile( _cpageFileSize );
if ( _cpageFileSize > 0 )
{
iCurrBorrow = 0;
pSrcNext = BorrowBuffer(iCurrBorrow, FALSE );
pDstNext = dst.BorrowNewBuffer(iCurrBorrow);
const unsigned iLastPage = _cpageFileSize-1;
for ( unsigned j = 0; j < _cpageFileSize; j++ )
{
ULONG * pSrc = pSrcNext;
ULONG * pDst = pDstNext;
Win4Assert( 0 != pSrc && 0 != pDst );
RtlCopyMemory( pDst, pSrc, CI_PAGE_SIZE );
//
// Before returning buffers, grab the next ones to prevent
// the large page from being taken out of cache.
//
if ( j != iLastPage )
{
iCurrBorrow = j+1;
pSrcNext = BorrowBuffer(iCurrBorrow, FALSE );
pDstNext = dst.BorrowNewBuffer(iCurrBorrow);
}
else
{
pSrcNext = pDstNext = 0;
}
ReturnBuffer( j );
dst.ReturnBuffer( j, TRUE );
if ( progressTracker.IsAbort() )
THROW( CException( STATUS_TOO_LATE ) );
//
// Update the progress every 64 K.
//
if ( (j % PAGES_PER_COMMON_PAGE) == (PAGES_PER_COMMON_PAGE-1) )
progressTracker.UpdateCopyProgress( (ULONG) j, _cpageFileSize );
}
}
dst.Flush();
}
CATCH( CException, e )
{
if ( pSrcNext )
ReturnBuffer( iCurrBorrow );
if ( pDstNext )
dst.ReturnBuffer(iCurrBorrow);
sc = e.GetErrorCode();
}
END_CATCH
if ( S_OK != sc )
{
//
// _GrowFile throws CI_CORRUPT_DATABASE if it cannot create
// the file that big. We don't want to consider that a
// corruption.
//
if ( CI_CORRUPT_DATABASE == sc )
sc = E_FAIL;
THROW( CException( sc ) );
}
//
// Consider 100% of the copy work is done.
//
progressTracker.UpdateCopyProgress( 1,1 );
}
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::~CPhysStorage, public
//
// Synopsis: closes index.
//
// History: 09-Mar-92 KyleP Created
//
// Notes: Don't write back pages. This is either a read only
// index or we are aborting a merge. Pages are written
// back after a successful merge using Reopen().
//
//--------------------------------------------------------------------------
CPhysStorage::~CPhysStorage()
{
ciDebugOut(( DEB_ITRACE, "Physical index %lx closed.\n", _objectId ));
_cache.Free( FALSE );
}
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::Close, public
//
// Effects: Closes the stream.
//
// History: 07-Mar-92 KyleP Created
//
//--------------------------------------------------------------------------
void CPhysStorage::Close()
{
_cache.Free();
_stream.Free();
}
//+---------------------------------------------------------------------------
//
// Function: Flush
//
// Synopsis: Flushes all the pages that were unmapped since the last
// flush.
//
// History: 4-29-94 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void CPhysStorage::Flush( BOOL fLeaveFlushFlagAlone )
{
Win4Assert( _fWritable );
// Grab the lock to make sure the pages aren't deleted while
// they are being flushed.
if ( 0 == _cMaxCachedBuffers )
{
CLock lock( _xMutex.GetReference() );
_cache.Flush( ULONG_MAX, fLeaveFlushFlagAlone );
}
else
{
CReadAccess readLock( _xRWAccess.GetReference() );
_cache.Flush( ULONG_MAX, fLeaveFlushFlagAlone );
}
} //Flush
//+---------------------------------------------------------------------------
//
// Function: ShrinkToFit
//
// Synopsis: Reduces the size of the stream by giving up pages in the end
// which are not needed.
//
// Arguments: (none)
//
// History: 9-08-94 srikants Created
//
// Notes:
//
//----------------------------------------------------------------------------
void CPhysStorage::ShrinkToFit()
{
_cache.Free();
_stream->SetSize( _storage,
PageToLow(_cpageUsedFileSize),
PageToHigh(_cpageUsedFileSize) );
_cpageFileSize = _cpageUsedFileSize;
}
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::Reopen, public
//
// Synopsis: Flushes, closes, and reopens for reading.
//
// History: 09-Mar-92 KyleP Created
// 26-Aug-92 BartoszM Separated from destructor
// 06-May-94 Srikants Add fSetSize as a parameter to truncate
// the stream optionally.
//
//--------------------------------------------------------------------------
void CPhysStorage::Reopen( BOOL fWritable )
{
// No need to lock.
_cache.Free(); // will flush all buffers
if ( _fWritable && !_stream.IsNull() )
{
//
// Give it an accurate used length.
//
_stream->SetSize( _storage,
PageToLow(_cpageUsedFileSize),
PageToHigh(_cpageUsedFileSize) );
_cpageFileSize = _cpageUsedFileSize;
_stream.Free();
//
// reopen shadow indexes as read and master indexes as write to
// support shrink from front.
//
_fWritable = fWritable;
}
//
// reopen it with the new mode
//
ReOpenStream();
if ( _stream.IsNull() || !_stream->Ok() )
{
ciDebugOut(( DEB_ERROR, "Re-Open file of index %08x failed\n",
_objectId ));
if ( _fThrowCorruptErrorOnFailures || _stream->FStatusFileNotFound() )
{
//
// We don't have code to handle such failures, hence mark
// catalog as corrupt; otherwise throw e_fail
//
Win4Assert( !"Corrupt catalog" );
_storage.ReportCorruptComponent( L"PhysStorage3" );
THROW( CException( CI_CORRUPT_DATABASE ));
}
else
THROW( CException( E_FAIL ));
}
_cpageFileSize = ToPages(_stream->SizeLow(), _stream->SizeHigh());
ciDebugOut(( DEB_ITRACE, "Physical index %08x opened.\n", _objectId ));
} //Reopen
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::LokBorrowNewBuffer, public
//
// Arguments: [hash] -- hash value for doing the lookup
// [commonPage]] -- the common page of nPage
// [nPage] -- page number
//
// Returns: A buffer for a new (unused) page.
//
// History: 03-Mar-98 dlee Created from existing BorrowNewBuffer
//
// Notes: On creation, the buffer is filled with zeros.
//
//--------------------------------------------------------------------------
ULONG * CPhysStorage::LokBorrowNewBuffer(
ULONG hash,
ULONG commonPage,
ULONG nPage )
{
CPhysBuffer* pBuf = _cache.Search( hash, commonPage );
if (pBuf)
{
pBuf->Reference();
Win4Assert( nPage < _cpageFileSize );
// fIntentToWrite should be TRUE
pBuf->SetIntentToWrite();
}
else
{
// If we've moved beyond the previous end of file, set the new
// size of file.
if ( nPage >= _cpageFileSize )
_GrowFile( nPage + _cpageGrowth );
// First try to release unreferenced buffers if the cache is full.
_cache.TryToRemoveBuffers( _cMaxCachedBuffers );
pBuf = new CPhysBuffer( _stream.GetReference(),
nPage,
TRUE, // writable
TRUE, // intent to write
! ( _fAllowReadAhead &&
_storage.FavorReadAhead() ) );
_cache.Add( pBuf, hash );
}
Win4Assert( nPage + 1 > _cpageUsedFileSize );
_cpageUsedFileSize = nPage + 1;
Win4Assert( _cpageUsedFileSize <= _cpageFileSize );
RtlZeroMemory( pBuf->GetPage( nPage ), CI_PAGE_SIZE );
return pBuf->GetPage( nPage );
} //LokBorrowNewBuffer
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::BorrowNewBuffer, public
//
// Arguments: [nPage] -- page number
//
// Returns: A buffer for a new (unused) page.
//
// Signals: No more disk space.
//
// History: 09-Mar-92 KyleP Created
//
// Notes: On creation, the buffer is filled with zeros.
//
//--------------------------------------------------------------------------
ULONG * CPhysStorage::BorrowNewBuffer( ULONG nPage )
{
Win4Assert( _fWritable );
ULONG commonPage = PgCommonPgTrunc( nPage );
ULONG hash = _cache.hash( commonPage );
// Use appropriate locking based on whether any pages are in the cache.
if ( 0 == _cMaxCachedBuffers )
{
CLock lock( _xMutex.GetReference() );
return LokBorrowNewBuffer( hash, commonPage, nPage );
}
else
{
CWriteAccess writeLock( _xRWAccess.GetReference() );
return LokBorrowNewBuffer( hash, commonPage, nPage );
}
} //BorrowNewBuffer
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::LokBorrowOrAddBuffer, public
//
// Arguments: [hash] -- hash value for doing the lookup
// [commonPage]] -- the common page of nPage
// [nPage] -- page number
// [fAdd] -- if TRUE, create the page if not found
// if FALSE and not found, return 0
// [fWritable] -- if TRUE, the page is writable
// [fIntentToWrite] -- TRUE if the caller intends to write
//
// Returns: A buffer loaded with page [nPage]
//
// History: 09-Mar-92 KyleP Created
//
//--------------------------------------------------------------------------
ULONG * CPhysStorage::LokBorrowOrAddBuffer(
ULONG hash,
ULONG commonPage,
ULONG nPage,
BOOL fAdd,
BOOL fWritable,
BOOL fIntentToWrite )
{
// First, look in the cache. Either we haven't looked yet (if no
// caching) or another thread may have added it between the time the
// read lock was released and the write lock taken.
Win4Assert( commonPage >= _iFirstNonShrunkPage );
CPhysBuffer * pbuf = _cache.Search( hash, commonPage );
if ( 0 != pbuf )
{
Win4Assert( !fWritable || pbuf->IsWritable() );
pbuf->Reference();
if ( fIntentToWrite )
pbuf->SetIntentToWrite();
return pbuf->GetPage( nPage );
}
ULONG * pulRet = 0;
if ( fAdd )
{
pbuf = new CPhysBuffer( _stream.GetReference(),
nPage,
fWritable,
fIntentToWrite,
! ( _fAllowReadAhead &&
_storage.FavorReadAhead() ) );
_cache.Add( pbuf, hash );
pulRet = pbuf->GetPage( nPage );
// Try to release unreferenced buffers if the cache is full.
_cache.TryToRemoveBuffers( _cMaxCachedBuffers );
}
return pulRet;
} //LokBorrowOrAddBuffer
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::BorrowBuffer, public
//
// Arguments: [nPage] -- Page number of buffer to find.
//
// Returns: A buffer loaded with page [nPage]
//
// Signals: Out of memory? Buffer not found?
//
// History: 09-Mar-92 KyleP Created
//
//--------------------------------------------------------------------------
ULONG * CPhysStorage::BorrowBuffer(
ULONG nPage,
BOOL fWritable,
BOOL fIntentToWrite )
{
ULONG commonPage = PgCommonPgTrunc( nPage );
ULONG hash = _cache.hash( commonPage );
Win4Assert( fWritable || !fIntentToWrite );
// Make sure we didn't walk off the end.
if ( nPage >= _cpageFileSize )
{
ciDebugOut(( DEB_WARN, "Asking for page %d, file size %d pages\n",
nPage, _cpageFileSize ));
Win4Assert( !"BorrowBuffer walked off end of file" );
_storage.ReportCorruptComponent( L"PhysStorage4" );
THROW( CException( CI_CORRUPT_DATABASE ) );
}
// If caching, try finding it in the cache first under a read lock
// This is the fast path for when the entire property cache is in memory.
if ( 0 != _cMaxCachedBuffers )
{
CReadAccess readLock( _xRWAccess.GetReference() );
ULONG * p = LokBorrowOrAddBuffer( hash, commonPage, nPage, FALSE,
fWritable, fIntentToWrite );
if ( 0 != p )
return p;
}
// Ok, maybe we have to add it.
if ( 0 == _cMaxCachedBuffers )
{
CLock lock( _xMutex.GetReference() );
return LokBorrowOrAddBuffer( hash, commonPage, nPage, TRUE,
fWritable, fIntentToWrite );
}
else
{
CWriteAccess writeLock( _xRWAccess.GetReference() );
return LokBorrowOrAddBuffer( hash, commonPage, nPage, TRUE,
fWritable, fIntentToWrite );
}
} //BorrowBuffer
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::ReturnBuffer, public
//
// Synopsis: Dereference and return the buffer to the pool.
//
// Arguments: [nPage] -- Page number of buffer being returned.
// [fFlush] -- TRUE if the buffer should be flushed if this
// is the last reference to the buffer.
// [fFailFlush] -- TRUE if an exception should be raised if
// the flush fails. Note that even if fFlush
// is FALSE, the buffer may still be flushed
// because previously it was borrowed with
// intent to write.
//
// Notes: If flush throws, this call doesn't return the buffer.
//
// History: 16-Mar-93 BartoszM Created
//
//--------------------------------------------------------------------------
void CPhysStorage::ReturnBuffer(
ULONG nPage,
BOOL fFlush,
BOOL fFailFlush )
{
ULONG commonPage = PgCommonPgTrunc( nPage );
ULONG hash = _cache.hash( commonPage );
// if caching is off, grab the write lock, else the read lock
if ( 0 == _cMaxCachedBuffers )
{
CLock lock( _xMutex.GetReference() );
Win4Assert( commonPage >= _iFirstNonShrunkPage );
CPhysBuffer *pbuf = _cache.Search( hash, commonPage );
Win4Assert( 0 != pbuf );
Win4Assert( pbuf->IsReferenced() );
// If the refcount is 1, it's about to be destroyed. Do the Flush
// now, in case it fails, before mucking with any data structures.
BOOL fDestroy = pbuf->IsRefCountOne();
if ( fDestroy && fFlush )
{
Win4Assert( pbuf->IsWritable() );
pbuf->Flush( fFailFlush );
}
pbuf->DeReference( _usnGen++ );
if ( fDestroy )
{
Win4Assert( !pbuf->IsReferenced() );
_cache.Destroy( nPage, fFailFlush );
Win4Assert( !_cache.Search( hash, commonPage ) );
}
}
else
{
CReadAccess readLock( _xRWAccess.GetReference() );
Win4Assert( commonPage >= _iFirstNonShrunkPage );
CPhysBuffer *pbuf = _cache.Search( hash, commonPage );
Win4Assert( 0 != pbuf );
Win4Assert( pbuf->IsReferenced() );
if ( fFlush )
{
Win4Assert( pbuf->IsWritable() );
if ( pbuf->IsRefCountOne() )
pbuf->Flush( fFailFlush );
}
pbuf->DeReference( _usnGen++ );
// leave it in the cache -- it'll be cleared out by BorrowBuffer
}
} //ReturnBuffer
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::RequiresFlush, public
//
// Synopsis: Determines if the page is scheduled for flushing
//
// Arguments: [nPage] -- Page number to check
//
// Returns: TRUE if the buffer will be flushed if Flush or Destroy is
// called.
//
// History: 3-Nov-98 dlee Created
//
//--------------------------------------------------------------------------
BOOL CPhysStorage::RequiresFlush( ULONG nPage )
{
ULONG commonPage = PgCommonPgTrunc( nPage );
ULONG hash = _cache.hash( commonPage );
// if caching is off, grab the write lock, else the read lock
if ( 0 == _cMaxCachedBuffers )
{
CLock lock( _xMutex.GetReference() );
Win4Assert( commonPage >= _iFirstNonShrunkPage );
CPhysBuffer *pbuf = _cache.Search( hash, commonPage );
Win4Assert( 0 != pbuf );
Win4Assert( pbuf->IsReferenced() );
return pbuf->RequiresFlush();
}
else
{
CReadAccess readLock( _xRWAccess.GetReference() );
Win4Assert( commonPage >= _iFirstNonShrunkPage );
CPhysBuffer *pbuf = _cache.Search( hash, commonPage );
Win4Assert( 0 != pbuf );
Win4Assert( pbuf->IsReferenced() );
return pbuf->RequiresFlush();
}
} //RequiresFlush
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::_GrowFile, private
//
// Synopsis: Increases the physical (disk) size of the file.
//
// Arguments: [cpageSize] -- New file size, in pages.
//
// Signals: Out of space.
//
// History: 09-Mar-92 KyleP Created
//
//--------------------------------------------------------------------------
void CPhysStorage::_GrowFile( ULONG cpageSize )
{
Win4Assert( cpageSize > 0 );
cpageSize = PgCommonPgRound(cpageSize);
ciDebugOut(( DEB_ITRACE, " Growing Index to %d pages\n", cpageSize ));
_stream->SetSize( _storage,
PageToLow(cpageSize),
PageToHigh(cpageSize) );
if (!_stream->Ok())
{
ciDebugOut(( DEB_ERROR, "GrowFile of index %08x failed: %d\n",
_objectId ));
if ( _fThrowCorruptErrorOnFailures )
{
//
// We don't have code to handle such failures, hence mark
// catalog as corrupt; otherwise throw e_fail
//
Win4Assert( !"Corrupt catalog" );
_storage.ReportCorruptComponent( L"PhysStorage5" );
THROW( CException( CI_CORRUPT_DATABASE ) );
}
else
THROW( CException( E_FAIL ) );
}
_cpageFileSize = cpageSize;
} //_GrowFile
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::ShrinkFromFront, public
//
// Synopsis: Makes the front part of a file sparse
//
// Arguments: [iFirstPage] -- first 4k page -- 64k granular
// [cPages] -- number of 4k pages
//
// Returns: The # of 4k pages actually shrunk, maybe 0
//
// History: 09-Jan-97 dlee Moved from .hxx
//
//--------------------------------------------------------------------------
ULONG CPhysStorage::ShrinkFromFront(
ULONG iFirstPage,
ULONG cPages )
{
ULONG cShrunk = 0;
if ( _storage.SupportsShrinkFromFront() )
{
//
// Make sure the caller isn't leaving any gaps and was paying
// attention to the return value from previous calls.
//
Win4Assert( iFirstPage == _iFirstNonShrunkPage );
//
// We must shrink on common page boundaries since we borrow on
// common pages (even though the api is page granular)
//
ULONG cPagesToShrink = PgCommonPgTrunc( cPages );
if ( 0 == cPagesToShrink )
return 0;
Win4Assert( _iFirstNonShrunkPage ==
PgCommonPgTrunc( _iFirstNonShrunkPage ) );
Win4Assert( iFirstPage == PgCommonPgTrunc( iFirstPage ) );
// Take a lock so no one else tries to borrow or free any pages
Win4Assert ( 0 == _cMaxCachedBuffers );
CLock lock( _xMutex.GetReference() );
cShrunk = _stream->ShrinkFromFront( iFirstPage, cPagesToShrink );
Win4Assert( cShrunk == PgCommonPgTrunc( cShrunk ) );
_iFirstNonShrunkPage += cShrunk;
}
return cShrunk;
} //ShrinkFromFront
//+-------------------------------------------------------------------------
//
// Member: CPhysStorage::MinPageInUse, public
//
// Synopsis: Finds the smallest page in use within the cache
//
// Arguments: [minPage] -- returns the result
//
// Returns : TRUE if any page is in the cache; FALSE o/w
// If TRUE is returned, then minPage will contain the
// minimum page that is present in the cache.
//
// History: 26-Mar-98 dlee Moved from .hxx
//
//--------------------------------------------------------------------------
BOOL CPhysStorage::MinPageInUse( ULONG &minPage )
{
if ( 0 == _cMaxCachedBuffers )
{
CLock lock( _xMutex.GetReference() );
return _cache.MinPageInUse(minPage);
}
else
{
CReadAccess readLock( _xRWAccess.GetReference() );
return _cache.MinPageInUse(minPage);
}
} //MinPageInUse