/****************************************************************************** * $Id: gdalrasterblock.cpp 29334 2015-06-14 17:30:54Z rouault $ * * Project: GDAL Core * Purpose: Implementation of GDALRasterBlock class and related global * raster block cache management. * Author: Frank Warmerdam, warmerdam@pobox.com * ********************************************************************** * Copyright (c) 1998, Frank Warmerdam * Copyright (c) 2008-2013, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "gdal_priv.h" #include "cpl_multiproc.h" CPL_CVSID("$Id: gdalrasterblock.cpp 29334 2015-06-14 17:30:54Z rouault $"); static int bCacheMaxInitialized = FALSE; static GIntBig nCacheMax = 40 * 1024*1024; static volatile GIntBig nCacheUsed = 0; static GDALRasterBlock *poOldest = NULL; /* tail */ static GDALRasterBlock *poNewest = NULL; /* head */ #if 0 static CPLMutex *hRBLock = NULL; #define INITIALIZE_LOCK CPLMutexHolderD( &hRBLock ) #define TAKE_LOCK CPLMutexHolderOptionalLockD( hRBLock ) #define DESTROY_LOCK CPLDestroyMutex( hRBLock ) #else static CPLLock* hRBLock = NULL; static int bDebugContention = FALSE; static CPLLockType GetLockType() { static int nLockType = -1; if( nLockType < 0 ) { const char* pszLockType = CPLGetConfigOption("GDAL_RB_LOCK_TYPE", "ADAPTIVE"); if( EQUAL(pszLockType, "ADAPTIVE") ) nLockType = LOCK_ADAPTIVE_MUTEX; else if( EQUAL(pszLockType, "RECURSIVE") ) nLockType = LOCK_RECURSIVE_MUTEX; else if( EQUAL(pszLockType, "SPIN") ) nLockType = LOCK_SPIN; else { CPLError(CE_Warning, CPLE_NotSupported, "GDAL_RB_LOCK_TYPE=%s not supported. Falling back to ADAPTIVE", pszLockType); nLockType = LOCK_ADAPTIVE_MUTEX; } bDebugContention = CSLTestBoolean(CPLGetConfigOption("GDAL_RB_LOCK_DEBUG_CONTENTION", "NO")); } return (CPLLockType) nLockType; } #define INITIALIZE_LOCK CPLLockHolderD( &hRBLock, GetLockType() ); \ CPLLockSetDebugPerf(hRBLock, bDebugContention) #define TAKE_LOCK CPLLockHolderOptionalLockD( hRBLock ) #define DESTROY_LOCK CPLDestroyLock( hRBLock ) #endif //#define ENABLE_DEBUG /************************************************************************/ /* GDALSetCacheMax() */ /************************************************************************/ /** * \brief Set maximum cache memory. * * This function sets the maximum amount of memory that GDAL is permitted * to use for GDALRasterBlock caching. The unit of the value is bytes. * * The maximum value is 2GB, due to the use of a signed 32 bit integer. * Use GDALSetCacheMax64() to be able to set a higher value. * * @param nNewSizeInBytes the maximum number of bytes for caching. */ void CPL_STDCALL GDALSetCacheMax( int nNewSizeInBytes ) { GDALSetCacheMax64(nNewSizeInBytes); } /************************************************************************/ /* GDALSetCacheMax64() */ /************************************************************************/ /** * \brief Set maximum cache memory. * * This function sets the maximum amount of memory that GDAL is permitted * to use for GDALRasterBlock caching. The unit of the value is bytes. * * Note: On 32 bit platforms, the maximum amount of memory that can be addressed * by a process might be 2 GB or 3 GB, depending on the operating system * capabilities. This function will not make any attempt to check the * consistency of the passed value with the effective capabilities of the OS. * * @param nNewSizeInBytes the maximum number of bytes for caching. * * @since GDAL 1.8.0 */ void CPL_STDCALL GDALSetCacheMax64( GIntBig nNewSizeInBytes ) { bCacheMaxInitialized = TRUE; nCacheMax = nNewSizeInBytes; /* -------------------------------------------------------------------- */ /* Flush blocks till we are under the new limit or till we */ /* can't seem to flush anymore. */ /* -------------------------------------------------------------------- */ while( nCacheUsed > nCacheMax ) { GIntBig nOldCacheUsed = nCacheUsed; GDALFlushCacheBlock(); if( nCacheUsed == nOldCacheUsed ) break; } } /************************************************************************/ /* GDALGetCacheMax() */ /************************************************************************/ /** * \brief Get maximum cache memory. * * Gets the maximum amount of memory available to the GDALRasterBlock * caching system for caching GDAL read/write imagery. * * The first type this function is called, it will read the GDAL_CACHEMAX * configuation option to initialize the maximum cache memory. * * This function cannot return a value higher than 2 GB. Use * GDALGetCacheMax64() to get a non-truncated value. * * @return maximum in bytes. */ int CPL_STDCALL GDALGetCacheMax() { GIntBig nRes = GDALGetCacheMax64(); if (nRes > INT_MAX) { static int bHasWarned = FALSE; if (!bHasWarned) { CPLError(CE_Warning, CPLE_AppDefined, "Cache max value doesn't fit on a 32 bit integer. " "Call GDALGetCacheMax64() instead"); bHasWarned = TRUE; } nRes = INT_MAX; } return (int)nRes; } /************************************************************************/ /* GDALGetCacheMax64() */ /************************************************************************/ /** * \brief Get maximum cache memory. * * Gets the maximum amount of memory available to the GDALRasterBlock * caching system for caching GDAL read/write imagery. * * The first type this function is called, it will read the GDAL_CACHEMAX * configuation option to initialize the maximum cache memory. * * @return maximum in bytes. * * @since GDAL 1.8.0 */ GIntBig CPL_STDCALL GDALGetCacheMax64() { if( !bCacheMaxInitialized ) { { INITIALIZE_LOCK; } const char* pszCacheMax = CPLGetConfigOption("GDAL_CACHEMAX",NULL); bCacheMaxInitialized = TRUE; if( pszCacheMax != NULL ) { GIntBig nNewCacheMax = (GIntBig)CPLScanUIntBig(pszCacheMax, strlen(pszCacheMax)); if( nNewCacheMax < 100000 ) { if (nNewCacheMax < 0) { CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for GDAL_CACHEMAX. Using default value."); return nCacheMax; } nNewCacheMax *= 1024 * 1024; } nCacheMax = nNewCacheMax; } } return nCacheMax; } /************************************************************************/ /* GDALGetCacheUsed() */ /************************************************************************/ /** * \brief Get cache memory used. * * @return the number of bytes of memory currently in use by the * GDALRasterBlock memory caching. */ int CPL_STDCALL GDALGetCacheUsed() { if (nCacheUsed > INT_MAX) { static int bHasWarned = FALSE; if (!bHasWarned) { CPLError(CE_Warning, CPLE_AppDefined, "Cache used value doesn't fit on a 32 bit integer. " "Call GDALGetCacheUsed64() instead"); bHasWarned = TRUE; } return INT_MAX; } return (int)nCacheUsed; } /************************************************************************/ /* GDALGetCacheUsed64() */ /************************************************************************/ /** * \brief Get cache memory used. * * @return the number of bytes of memory currently in use by the * GDALRasterBlock memory caching. * * @since GDAL 1.8.0 */ GIntBig CPL_STDCALL GDALGetCacheUsed64() { return nCacheUsed; } /************************************************************************/ /* GDALFlushCacheBlock() */ /* */ /* The workhorse of cache management! */ /************************************************************************/ /** * \brief Try to flush one cached raster block * * This function will search the first unlocked raster block and will * flush it to release the associated memory. * * @return TRUE if one block was flushed, FALSE if there are no cached blocks * or if they are currently locked. */ int CPL_STDCALL GDALFlushCacheBlock() { return GDALRasterBlock::FlushCacheBlock(); } /************************************************************************/ /* ==================================================================== */ /* GDALRasterBlock */ /* ==================================================================== */ /************************************************************************/ /** * \class GDALRasterBlock "gdal_priv.h" * * GDALRasterBlock objects hold one block of raster data for one band * that is currently stored in the GDAL raster cache. The cache holds * some blocks of raster data for zero or more GDALRasterBand objects * across zero or more GDALDataset objects in a global raster cache with * a least recently used (LRU) list and an upper cache limit (see * GDALSetCacheMax()) under which the cache size is normally kept. * * Some blocks in the cache may be modified relative to the state on disk * (they are marked "Dirty") and must be flushed to disk before they can * be discarded. Other (Clean) blocks may just be discarded if their memory * needs to be recovered. * * In normal situations applications do not interact directly with the * GDALRasterBlock - instead it it utilized by the RasterIO() interfaces * to implement caching. * * Some driver classes are implemented in a fashion that completely avoids * use of the GDAL raster cache (and GDALRasterBlock) though this is not very * common. */ /************************************************************************/ /* FlushCacheBlock() */ /* */ /* Note, if we have alot of blocks locked for a long time, this */ /* method is going to get slow because it will have to traverse */ /* the linked list a long ways looking for a flushing */ /* candidate. It might help to re-touch locked blocks to push */ /* them to the top of the list. */ /************************************************************************/ /** * \brief Attempt to flush at least one block from the cache. * * This static method is normally used to recover memory when a request * for a new cache block would put cache memory use over the established * limit. * * C++ analog to the C function GDALFlushCacheBlock(). * * @param bDirtyBlocksOnly Only flushes dirty blocks. * @return TRUE if successful or FALSE if no flushable block is found. */ int GDALRasterBlock::FlushCacheBlock(int bDirtyBlocksOnly) { GDALRasterBlock *poTarget; { INITIALIZE_LOCK; poTarget = poOldest; while( poTarget != NULL && (poTarget->GetLockCount() > 0 || (bDirtyBlocksOnly && !poTarget->GetDirty())) ) poTarget = poTarget->poPrevious; if( poTarget == NULL ) return FALSE; poTarget->Detach_unlocked(); poTarget->GetBand()->UnreferenceBlock(poTarget->GetXOff(),poTarget->GetYOff()); } if( poTarget->GetDirty() ) { CPLErr eErr = poTarget->Write(); if( eErr != CE_None ) { /* Save the error for later reporting */ poTarget->GetBand()->SetFlushBlockErr(eErr); } } delete poTarget; return TRUE; } /************************************************************************/ /* FlushDirtyBlocks() */ /************************************************************************/ /** * \brief Flush all dirty blocks from cache. * * This static method is normally used to recover memory and is especially * useful when doing multi-threaded code that can trigger the block cache. * * Due to the current design of the block cache, dirty blocks belonging to a same * dataset could be pushed simultanously to the IWriteBlock() method of that * dataset from different threads, causing races. * * Calling this method before that code can help workarounding that issue, * in a multiple readers, one writer scenario. * * @since GDAL 2.0 */ void GDALRasterBlock::FlushDirtyBlocks() { while( FlushCacheBlock(TRUE) ) { /* go on */ } } /************************************************************************/ /* GDALRasterBlock() */ /************************************************************************/ /** * @brief GDALRasterBlock Constructor * * Normally only called from GDALRasterBand::GetLockedBlockRef(). * * @param poBandIn the raster band used as source of raster block * being constructed. * * @param nXOffIn the horizontal block offset, with zero indicating * the left most block, 1 the next block and so forth. * * @param nYOffIn the vertical block offset, with zero indicating * the top most block, 1 the next block and so forth. */ GDALRasterBlock::GDALRasterBlock( GDALRasterBand *poBandIn, int nXOffIn, int nYOffIn ) { CPLAssert( NULL != poBandIn ); poBand = poBandIn; poBand->GetBlockSize( &nXSize, &nYSize ); eType = poBand->GetRasterDataType(); pData = NULL; bDirty = FALSE; nLockCount = 0; poNext = poPrevious = NULL; nXOff = nXOffIn; nYOff = nYOffIn; bMustDetach = TRUE; } /************************************************************************/ /* ~GDALRasterBlock() */ /************************************************************************/ /** * Block destructor. * * Normally called from GDALRasterBand::FlushBlock(). */ GDALRasterBlock::~GDALRasterBlock() { Detach(); if( pData != NULL ) { VSIFree( pData ); } CPLAssert( nLockCount == 0 ); #ifdef ENABLE_DEBUG Verify(); #endif } /************************************************************************/ /* Detach() */ /************************************************************************/ /** * Remove block from cache. * * This method removes the current block from the linked list used to keep * track of all cached blocks in order of age. It does not affect whether * the block is referenced by a GDALRasterBand nor does it destroy or flush * the block. */ void GDALRasterBlock::Detach() { if( bMustDetach ) { TAKE_LOCK; Detach_unlocked(); } } void GDALRasterBlock::Detach_unlocked() { if( poOldest == this ) poOldest = poPrevious; if( poNewest == this ) { poNewest = poNext; } if( poPrevious != NULL ) poPrevious->poNext = poNext; if( poNext != NULL ) poNext->poPrevious = poPrevious; poPrevious = NULL; poNext = NULL; bMustDetach = FALSE; if( pData ) nCacheUsed -= GetBlockSize(); #ifdef ENABLE_DEBUG Verify(); #endif } /************************************************************************/ /* Verify() */ /************************************************************************/ /** * Confirms (via assertions) that the block cache linked list is in a * consistent state. */ void GDALRasterBlock::Verify() { TAKE_LOCK; CPLAssert( (poNewest == NULL && poOldest == NULL) || (poNewest != NULL && poOldest != NULL) ); if( poNewest != NULL ) { CPLAssert( poNewest->poPrevious == NULL ); CPLAssert( poOldest->poNext == NULL ); GDALRasterBlock* poLast = NULL; for( GDALRasterBlock *poBlock = poNewest; poBlock != NULL; poBlock = poBlock->poNext ) { CPLAssert( poBlock->poPrevious == poLast ); poLast = poBlock; } CPLAssert( poOldest == poLast ); } } /************************************************************************/ /* Write() */ /************************************************************************/ /** * Force writing of the current block, if dirty. * * The block is written using GDALRasterBand::IWriteBlock() on it's * corresponding band object. Even if the write fails the block will * be marked clean. * * @return CE_None otherwise the error returned by IWriteBlock(). */ CPLErr GDALRasterBlock::Write() { if( !GetDirty() ) return CE_None; if( poBand == NULL ) return CE_Failure; MarkClean(); if (poBand->eFlushBlockErr == CE_None) { int bCallLeaveReadWrite = poBand->EnterReadWrite(GF_Write); CPLErr eErr = poBand->IWriteBlock( nXOff, nYOff, pData ); if( bCallLeaveReadWrite ) poBand->LeaveReadWrite(); return eErr; } else return poBand->eFlushBlockErr; } /************************************************************************/ /* Touch() */ /************************************************************************/ /** * Push block to top of LRU (least-recently used) list. * * This method is normally called when a block is used to keep track * that it has been recently used. */ void GDALRasterBlock::Touch() { TAKE_LOCK; Touch_unlocked(); } void GDALRasterBlock::Touch_unlocked() { if( poNewest == this ) return; // In theory, we shouldn't try to touch a block that has been detached CPLAssert(bMustDetach); if( !bMustDetach ) { if( pData ) nCacheUsed += GetBlockSize(); bMustDetach = TRUE; } if( poOldest == this ) poOldest = this->poPrevious; if( poPrevious != NULL ) poPrevious->poNext = poNext; if( poNext != NULL ) poNext->poPrevious = poPrevious; poPrevious = NULL; poNext = poNewest; if( poNewest != NULL ) { CPLAssert( poNewest->poPrevious == NULL ); poNewest->poPrevious = this; } poNewest = this; if( poOldest == NULL ) { CPLAssert( poPrevious == NULL && poNext == NULL ); poOldest = this; } #ifdef ENABLE_DEBUG Verify(); #endif } /************************************************************************/ /* Internalize() */ /************************************************************************/ /** * Allocate memory for block. * * This method allocates memory for the block, and attempts to flush other * blocks, if necessary, to bring the total cache size back within the limits. * The newly allocated block is touched and will be considered most recently * used in the LRU list. * * @return CE_None on success or CE_Failure if memory allocation fails. */ CPLErr GDALRasterBlock::Internalize() { void *pNewData = NULL; int nSizeInBytes; CPLAssert( pData == NULL ); // This call will initialize the hRBLock mutex. Other call places can // only be called if we have go through there. GIntBig nCurCacheMax = GDALGetCacheMax64(); /* No risk of overflow as it is checked in GDALRasterBand::InitBlockInfo() */ nSizeInBytes = GetBlockSize(); /* -------------------------------------------------------------------- */ /* Flush old blocks if we are nearing our memory limit. */ /* -------------------------------------------------------------------- */ int bFirstIter = TRUE; int bLoopAgain; do { bLoopAgain = FALSE; GDALRasterBlock* apoBlocksToFree[64]; int nBlocksToFree = 0; { TAKE_LOCK; if( bFirstIter ) nCacheUsed += nSizeInBytes; GDALRasterBlock *poTarget = poOldest; while( nCacheUsed > nCurCacheMax ) { while( poTarget != NULL && poTarget->GetLockCount() > 0 ) poTarget = poTarget->poPrevious; if( poTarget != NULL ) { GDALRasterBlock* _poPrevious = poTarget->poPrevious; poTarget->Detach_unlocked(); poTarget->GetBand()->UnreferenceBlock(poTarget->GetXOff(),poTarget->GetYOff()); apoBlocksToFree[nBlocksToFree++] = poTarget; if( poTarget->GetDirty() ) { // Only free one dirty block at a time so that // other dirty blocks of other bands with the same coordinates // can be found with TryGetLockedBlock() bLoopAgain = ( nCacheUsed > nCurCacheMax ); break; } if( nBlocksToFree == 64 ) { CPLDebug("GDAL", "More than 64 blocks are flagged to be flushed. Not trying more"); break; } poTarget = _poPrevious; } else break; } /* -------------------------------------------------------------------- */ /* Add this block to the list. */ /* -------------------------------------------------------------------- */ if( !bLoopAgain ) Touch_unlocked(); } bFirstIter = FALSE; /* Now free blocks we have detached and removed from their band */ for(int i=0;iGetDirty() ) { CPLErr eErr = poBlock->Write(); if( eErr != CE_None ) { /* Save the error for later reporting */ poBlock->GetBand()->SetFlushBlockErr(eErr); } } /* Try to recycle the data of an existing block */ void* pDataBlock = poBlock->pData; if( pNewData == NULL && pDataBlock != NULL && poBlock->GetBlockSize() >= nSizeInBytes ) { pNewData = pDataBlock; poBlock->pData = NULL; } delete poBlock; } } while(bLoopAgain); if( pNewData == NULL ) { pNewData = VSIMalloc( nSizeInBytes ); if( pNewData == NULL ) { CPLError( CE_Failure, CPLE_OutOfMemory, "GDALRasterBlock::Internalize : Out of memory allocating %d bytes.", nSizeInBytes); return( CE_Failure ); } } pData = pNewData; return( CE_None ); } /************************************************************************/ /* MarkDirty() */ /************************************************************************/ /** * Mark the block as modified. * * A dirty block is one that has been modified and will need to be written * to disk before it can be flushed. */ void GDALRasterBlock::MarkDirty() { bDirty = TRUE; } /************************************************************************/ /* MarkClean() */ /************************************************************************/ /** * Mark the block as unmodified. * * A dirty block is one that has been modified and will need to be written * to disk before it can be flushed. */ void GDALRasterBlock::MarkClean() { bDirty = FALSE; } /************************************************************************/ /* SafeLockBlock() */ /************************************************************************/ /** * \brief Safely lock block. * * This method locks a GDALRasterBlock (and touches it) in a thread-safe * manner. The global block cache mutex is held while locking the block, * in order to avoid race conditions with other threads that might be * trying to expire the block at the same time. The block pointer may be * safely NULL, in which case this method does nothing. * * @param ppBlock Pointer to the block pointer to try and lock/touch. */ int GDALRasterBlock::SafeLockBlock( GDALRasterBlock ** ppBlock ) { CPLAssert( NULL != ppBlock ); TAKE_LOCK; if( *ppBlock != NULL ) { (*ppBlock)->AddLock(); (*ppBlock)->Touch_unlocked(); return TRUE; } else return FALSE; } /************************************************************************/ /* DestroyRBMutex() */ /************************************************************************/ void GDALRasterBlock::DestroyRBMutex() { if( hRBLock != NULL ) DESTROY_LOCK; hRBLock = NULL; }