ultimatepp/bazaar/plugin/gdal/gcore/gdalrasterblock.cpp
cxl 23ff1e7e82 .gdal moved to bazaar
git-svn-id: svn://ultimatepp.org/upp/trunk@9273 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2015-12-07 13:36:24 +00:00

883 lines
27 KiB
C++

/******************************************************************************
* $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 <warmerdam@pobox.com>
* Copyright (c) 2008-2013, Even Rouault <even dot rouault at mines-paris dot org>
*
* 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;i<nBlocksToFree;i++)
{
GDALRasterBlock *poBlock = apoBlocksToFree[i];
if( poBlock->GetDirty() )
{
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;
}