/******************************************************************************
 * $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;
}
