Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/gcore/gdalrasterblock.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL Core
4
 * Purpose:  Implementation of GDALRasterBlock class and related global
5
 *           raster block cache management.
6
 * Author:   Frank Warmerdam, warmerdam@pobox.com
7
 *
8
 **********************************************************************
9
 * Copyright (c) 1998, Frank Warmerdam <warmerdam@pobox.com>
10
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "cpl_port.h"
16
#include "gdal.h"
17
#include "gdal_priv.h"
18
19
#include <algorithm>
20
#include <climits>
21
#include <cstring>
22
#include <mutex>
23
24
#include "cpl_atomic_ops.h"
25
#include "cpl_conv.h"
26
#include "cpl_error.h"
27
#include "cpl_multiproc.h"
28
#include "cpl_string.h"
29
#include "cpl_vsi.h"
30
31
// Will later be overridden by the default 5% if GDAL_CACHEMAX not defined.
32
static GIntBig nCacheMax = 40 * 1024 * 1024;
33
static GIntBig nCacheUsed = 0;
34
35
static GDALRasterBlock *poOldest = nullptr;  // Tail.
36
static GDALRasterBlock *poNewest = nullptr;  // Head.
37
38
static int nDisableDirtyBlockFlushCounter = 0;
39
40
#if 0
41
static CPLMutex *hRBLock = nullptr;
42
#define INITIALIZE_LOCK CPLMutexHolderD(&hRBLock)
43
#define TAKE_LOCK CPLMutexHolderOptionalLockD(hRBLock)
44
#define DESTROY_LOCK CPLDestroyMutex(hRBLock)
45
#else
46
47
static CPLLock *hRBLock = nullptr;
48
static bool bDebugContention = false;
49
static bool bSleepsForBockCacheDebug = false;
50
51
static CPLLockType GetLockType()
52
0
{
53
0
    static int nLockType = -1;
54
0
    if (nLockType < 0)
55
0
    {
56
0
        const char *pszLockType =
57
0
            CPLGetConfigOption("GDAL_RB_LOCK_TYPE", "ADAPTIVE");
58
0
        if (EQUAL(pszLockType, "ADAPTIVE"))
59
0
            nLockType = LOCK_ADAPTIVE_MUTEX;
60
0
        else if (EQUAL(pszLockType, "RECURSIVE"))
61
0
            nLockType = LOCK_RECURSIVE_MUTEX;
62
0
        else if (EQUAL(pszLockType, "SPIN"))
63
0
            nLockType = LOCK_SPIN;
64
0
        else
65
0
        {
66
0
            CPLError(
67
0
                CE_Warning, CPLE_NotSupported,
68
0
                "GDAL_RB_LOCK_TYPE=%s not supported. Falling back to ADAPTIVE",
69
0
                pszLockType);
70
0
            nLockType = LOCK_ADAPTIVE_MUTEX;
71
0
        }
72
0
        bDebugContention = CPLTestBool(
73
0
            CPLGetConfigOption("GDAL_RB_LOCK_DEBUG_CONTENTION", "NO"));
74
0
    }
75
0
    return static_cast<CPLLockType>(nLockType);
76
0
}
77
78
#define INITIALIZE_LOCK                                                        \
79
0
    CPLLockHolderD(&hRBLock, GetLockType());                                   \
80
0
    CPLLockSetDebugPerf(hRBLock, bDebugContention)
81
0
#define TAKE_LOCK CPLLockHolderOptionalLockD(hRBLock)
82
0
#define DESTROY_LOCK CPLDestroyLock(hRBLock)
83
84
#endif
85
86
// #define ENABLE_DEBUG
87
88
/************************************************************************/
89
/*                          GDALSetCacheMax()                           */
90
/************************************************************************/
91
92
/**
93
 * \brief Set maximum cache memory.
94
 *
95
 * This function sets the maximum amount of memory that GDAL is permitted
96
 * to use for GDALRasterBlock caching. The unit of the value is bytes.
97
 *
98
 * The maximum value is 2GB, due to the use of a signed 32 bit integer.
99
 * Use GDALSetCacheMax64() to be able to set a higher value.
100
 *
101
 * @param nNewSizeInBytes the maximum number of bytes for caching.
102
 */
103
104
void CPL_STDCALL GDALSetCacheMax(int nNewSizeInBytes)
105
106
0
{
107
0
    GDALSetCacheMax64(nNewSizeInBytes);
108
0
}
109
110
/************************************************************************/
111
/*                        GDALSetCacheMax64()                           */
112
/************************************************************************/
113
114
/**
115
 * \brief Set maximum cache memory.
116
 *
117
 * This function sets the maximum amount of memory that GDAL is permitted
118
 * to use for GDALRasterBlock caching. The unit of the value is bytes.
119
 *
120
 * Note: On 32 bit platforms, the maximum amount of memory that can be addressed
121
 * by a process might be 2 GB or 3 GB, depending on the operating system
122
 * capabilities. This function will not make any attempt to check the
123
 * consistency of the passed value with the effective capabilities of the OS.
124
 *
125
 * @param nNewSizeInBytes the maximum number of bytes for caching.
126
 *
127
 */
128
129
void CPL_STDCALL GDALSetCacheMax64(GIntBig nNewSizeInBytes)
130
131
0
{
132
#if 0
133
    if( nNewSizeInBytes == 12346789 )
134
    {
135
        GDALRasterBlock::DumpAll();
136
        return;
137
    }
138
#endif
139
140
    // To force one-time initialization of nCacheMax if not already done
141
0
    GDALGetCacheMax64();
142
0
    nCacheMax = nNewSizeInBytes;
143
144
    /* -------------------------------------------------------------------- */
145
    /*      Flush blocks till we are under the new limit or till we         */
146
    /*      can't seem to flush anymore.                                    */
147
    /* -------------------------------------------------------------------- */
148
0
    while (nCacheUsed > nCacheMax)
149
0
    {
150
0
        const GIntBig nOldCacheUsed = nCacheUsed;
151
152
0
        GDALFlushCacheBlock();
153
154
0
        if (nCacheUsed == nOldCacheUsed)
155
0
            break;
156
0
    }
157
0
}
158
159
/************************************************************************/
160
/*                          GDALGetCacheMax()                           */
161
/************************************************************************/
162
163
/**
164
 * \brief Get maximum cache memory.
165
 *
166
 * Gets the maximum amount of memory available to the GDALRasterBlock
167
 * caching system for caching GDAL read/write imagery.
168
 *
169
 * The first type this function is called, it will read the GDAL_CACHEMAX
170
 * configuration option to initialize the maximum cache memory.
171
 * The value can be expressed as x% of the usable
172
 * physical RAM (which may potentially be used by other processes). Otherwise
173
 * it is expected to be a value in MB.
174
 *
175
 * This function cannot return a value higher than 2 GB. Use
176
 * GDALGetCacheMax64() to get a non-truncated value.
177
 *
178
 * @return maximum in bytes.
179
 */
180
181
int CPL_STDCALL GDALGetCacheMax()
182
0
{
183
0
    GIntBig nRes = GDALGetCacheMax64();
184
0
    if (nRes > INT_MAX)
185
0
    {
186
0
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
187
0
                     "Cache max value doesn't fit on a 32 bit integer. "
188
0
                     "Call GDALGetCacheMax64() instead");
189
0
        nRes = INT_MAX;
190
0
    }
191
0
    return static_cast<int>(nRes);
192
0
}
193
194
/************************************************************************/
195
/*                         GDALGetCacheMax64()                          */
196
/************************************************************************/
197
198
/**
199
 * \brief Get maximum cache memory.
200
 *
201
 * Gets the maximum amount of memory available to the GDALRasterBlock
202
 * caching system for caching GDAL read/write imagery.
203
 *
204
 * The first time this function is called, it will read the GDAL_CACHEMAX
205
 * configuration option to initialize the maximum cache memory.
206
 * The value can be expressed as x% of the usable
207
 * physical RAM (which may potentially be used by other processes). Starting
208
 * with GDAL 3.11, the value can include units of memory. If not units are
209
 * provided the value is assumed to be in MB.
210
 *
211
 * @return maximum in bytes.
212
 *
213
 */
214
215
GIntBig CPL_STDCALL GDALGetCacheMax64()
216
0
{
217
0
    static std::once_flag flagSetupGDALGetCacheMax64;
218
0
    std::call_once(
219
0
        flagSetupGDALGetCacheMax64,
220
0
        []()
221
0
        {
222
0
            {
223
0
                INITIALIZE_LOCK;
224
0
            }
225
0
            bSleepsForBockCacheDebug =
226
0
                CPLTestBool(CPLGetConfigOption("GDAL_DEBUG_BLOCK_CACHE", "NO"));
227
228
0
            const char *pszCacheMax = CPLGetConfigOption("GDAL_CACHEMAX", "5%");
229
0
            GIntBig nNewCacheMax;
230
0
            bool bUnitSpecified = false;
231
232
0
            if (CPLParseMemorySize(pszCacheMax, &nNewCacheMax,
233
0
                                   &bUnitSpecified) != CE_None)
234
0
            {
235
0
                CPLError(CE_Failure, CPLE_NotSupported,
236
0
                         "Invalid value for GDAL_CACHEMAX. "
237
0
                         "Using default value.");
238
0
                if (CPLParseMemorySize("5%", &nNewCacheMax, &bUnitSpecified) !=
239
0
                    CE_None)
240
0
                {
241
                    // This means that usable physical RAM could not be determined.
242
0
                    nNewCacheMax = nCacheMax;
243
0
                }
244
0
            }
245
246
0
            if (!bUnitSpecified && nNewCacheMax < 100000)
247
0
            {
248
                // Assume MB
249
0
                nNewCacheMax *= (1024 * 1024);
250
0
            }
251
252
0
            nCacheMax = nNewCacheMax;
253
0
            CPLDebug("GDAL", "GDAL_CACHEMAX = " CPL_FRMT_GIB " MB",
254
0
                     nCacheMax / (1024 * 1024));
255
0
        });
256
257
0
    return nCacheMax;
258
0
}
259
260
/************************************************************************/
261
/*                          GDALGetCacheUsed()                          */
262
/************************************************************************/
263
264
/**
265
 * \brief Get cache memory used.
266
 *
267
 * @return the number of bytes of memory currently in use by the
268
 * GDALRasterBlock memory caching.
269
 */
270
271
int CPL_STDCALL GDALGetCacheUsed()
272
0
{
273
0
    if (nCacheUsed > INT_MAX)
274
0
    {
275
0
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
276
0
                     "Cache used value doesn't fit on a 32 bit integer. "
277
0
                     "Call GDALGetCacheUsed64() instead");
278
0
        return INT_MAX;
279
0
    }
280
0
    return static_cast<int>(nCacheUsed);
281
0
}
282
283
/************************************************************************/
284
/*                        GDALGetCacheUsed64()                          */
285
/************************************************************************/
286
287
/**
288
 * \brief Get cache memory used.
289
 *
290
 * @return the number of bytes of memory currently in use by the
291
 * GDALRasterBlock memory caching.
292
 *
293
 */
294
295
GIntBig CPL_STDCALL GDALGetCacheUsed64()
296
0
{
297
0
    return nCacheUsed;
298
0
}
299
300
/************************************************************************/
301
/*                        GDALFlushCacheBlock()                         */
302
/*                                                                      */
303
/*      The workhorse of cache management!                              */
304
/************************************************************************/
305
306
/**
307
 * \brief Try to flush one cached raster block
308
 *
309
 * This function will search the first unlocked raster block and will
310
 * flush it to release the associated memory.
311
 *
312
 * @return TRUE if one block was flushed, FALSE if there are no cached blocks
313
 *         or if they are currently locked.
314
 */
315
int CPL_STDCALL GDALFlushCacheBlock()
316
317
0
{
318
0
    return GDALRasterBlock::FlushCacheBlock();
319
0
}
320
321
/************************************************************************/
322
/* ==================================================================== */
323
/*                           GDALRasterBlock                            */
324
/* ==================================================================== */
325
/************************************************************************/
326
327
/**
328
 * \class GDALRasterBlock "gdal_priv.h"
329
 *
330
 * GDALRasterBlock objects hold one block of raster data for one band
331
 * that is currently stored in the GDAL raster cache.  The cache holds
332
 * some blocks of raster data for zero or more GDALRasterBand objects
333
 * across zero or more GDALDataset objects in a global raster cache with
334
 * a least recently used (LRU) list and an upper cache limit (see
335
 * GDALSetCacheMax()) under which the cache size is normally kept.
336
 *
337
 * Some blocks in the cache may be modified relative to the state on disk
338
 * (they are marked "Dirty") and must be flushed to disk before they can
339
 * be discarded.  Other (Clean) blocks may just be discarded if their memory
340
 * needs to be recovered.
341
 *
342
 * In normal situations applications do not interact directly with the
343
 * GDALRasterBlock - instead it it utilized by the RasterIO() interfaces
344
 * to implement caching.
345
 *
346
 * Some driver classes are implemented in a fashion that completely avoids
347
 * use of the GDAL raster cache (and GDALRasterBlock) though this is not very
348
 * common.
349
 */
350
351
/************************************************************************/
352
/*                          FlushCacheBlock()                           */
353
/*                                                                      */
354
/*      Note, if we have a lot of blocks locked for a long time, this    */
355
/*      method is going to get slow because it will have to traverse    */
356
/*      the linked list a long ways looking for a flushing              */
357
/*      candidate.   It might help to re-touch locked blocks to push    */
358
/*      them to the top of the list.                                    */
359
/************************************************************************/
360
361
/**
362
 * \brief Attempt to flush at least one block from the cache.
363
 *
364
 * This static method is normally used to recover memory when a request
365
 * for a new cache block would put cache memory use over the established
366
 * limit.
367
 *
368
 * C++ analog to the C function GDALFlushCacheBlock().
369
 *
370
 * @param bDirtyBlocksOnly Only flushes dirty blocks.
371
 * @return TRUE if successful or FALSE if no flushable block is found.
372
 */
373
374
int GDALRasterBlock::FlushCacheBlock(int bDirtyBlocksOnly)
375
376
0
{
377
0
    GDALRasterBlock *poTarget;
378
379
0
    {
380
0
        INITIALIZE_LOCK;
381
0
        poTarget = poOldest;
382
383
0
        while (poTarget != nullptr)
384
0
        {
385
0
            if (!bDirtyBlocksOnly ||
386
0
                (poTarget->GetDirty() && nDisableDirtyBlockFlushCounter == 0))
387
0
            {
388
0
                if (CPLAtomicCompareAndExchange(&(poTarget->nLockCount), 0, -1))
389
0
                    break;
390
0
            }
391
0
            poTarget = poTarget->poPrevious;
392
0
        }
393
394
0
        if (poTarget == nullptr)
395
0
            return FALSE;
396
0
#ifndef __COVERITY__
397
        // Disabled to avoid complains about sleeping under locks, that
398
        // are only true for debug/testing code
399
0
        if (bSleepsForBockCacheDebug)
400
0
        {
401
0
            const double dfDelay = CPLAtof(CPLGetConfigOption(
402
0
                "GDAL_RB_FLUSHBLOCK_SLEEP_AFTER_DROP_LOCK", "0"));
403
0
            if (dfDelay > 0)
404
0
                CPLSleep(dfDelay);
405
0
        }
406
0
#endif
407
408
0
        poTarget->Detach_unlocked();
409
0
        poTarget->GetBand()->UnreferenceBlock(poTarget);
410
0
    }
411
412
0
#ifndef __COVERITY__
413
    // Disabled to avoid complains about sleeping under locks, that
414
    // are only true for debug/testing code
415
0
    if (bSleepsForBockCacheDebug)
416
0
    {
417
0
        const double dfDelay = CPLAtof(
418
0
            CPLGetConfigOption("GDAL_RB_FLUSHBLOCK_SLEEP_AFTER_RB_LOCK", "0"));
419
0
        if (dfDelay > 0)
420
0
            CPLSleep(dfDelay);
421
0
    }
422
0
#endif
423
424
0
    if (poTarget->GetDirty())
425
0
    {
426
0
        const CPLErr eErr = poTarget->Write();
427
0
        if (eErr != CE_None)
428
0
        {
429
            // Save the error for later reporting.
430
0
            poTarget->GetBand()->SetFlushBlockErr(eErr);
431
0
        }
432
0
    }
433
434
0
    VSIFreeAligned(poTarget->pData);
435
0
    poTarget->pData = nullptr;
436
0
    poTarget->GetBand()->AddBlockToFreeList(poTarget);
437
438
0
    return TRUE;
439
0
}
440
441
/************************************************************************/
442
/*                          FlushDirtyBlocks()                          */
443
/************************************************************************/
444
445
/**
446
 * \brief Flush all dirty blocks from cache.
447
 *
448
 * This static method is normally used to recover memory and is especially
449
 * useful when doing multi-threaded code that can trigger the block cache.
450
 *
451
 * Due to the current design of the block cache, dirty blocks belonging to a
452
 * same dataset could be pushed simultaneously to the IWriteBlock() method of
453
 * that dataset from different threads, causing races.
454
 *
455
 * Calling this method before that code can help to work around that issue,
456
 * in a multiple readers, one writer scenario.
457
 *
458
 */
459
460
void GDALRasterBlock::FlushDirtyBlocks()
461
462
0
{
463
0
    while (FlushCacheBlock(TRUE))
464
0
    {
465
        /* go on */
466
0
    }
467
0
}
468
469
/************************************************************************/
470
/*                      EnterDisableDirtyBlockFlush()                   */
471
/************************************************************************/
472
473
/**
474
 * \brief Starts preventing dirty blocks from being flushed
475
 *
476
 * This static method is used to prevent dirty blocks from being flushed.
477
 * This might be useful when in a IWriteBlock() method, whose implementation
478
 * can directly/indirectly cause the block cache to evict new blocks, to
479
 * be recursively called on the same dataset.
480
 *
481
 * This method implements a reference counter and is thread-safe.
482
 *
483
 * This call must be paired with a corresponding LeaveDisableDirtyBlockFlush().
484
 *
485
 */
486
487
void GDALRasterBlock::EnterDisableDirtyBlockFlush()
488
0
{
489
0
    CPLAtomicInc(&nDisableDirtyBlockFlushCounter);
490
0
}
491
492
/************************************************************************/
493
/*                      LeaveDisableDirtyBlockFlush()                   */
494
/************************************************************************/
495
496
/**
497
 * \brief Ends preventing dirty blocks from being flushed.
498
 *
499
 * Undoes the effect of EnterDisableDirtyBlockFlush().
500
 *
501
 */
502
503
void GDALRasterBlock::LeaveDisableDirtyBlockFlush()
504
0
{
505
0
    CPLAtomicDec(&nDisableDirtyBlockFlushCounter);
506
0
}
507
508
/************************************************************************/
509
/*                          GDALRasterBlock()                           */
510
/************************************************************************/
511
512
/**
513
 * @brief GDALRasterBlock Constructor
514
 *
515
 * Normally only called from GDALRasterBand::GetLockedBlockRef().
516
 *
517
 * @param poBandIn the raster band used as source of raster block
518
 * being constructed.
519
 *
520
 * @param nXOffIn the horizontal block offset, with zero indicating
521
 * the left most block, 1 the next block and so forth.
522
 *
523
 * @param nYOffIn the vertical block offset, with zero indicating
524
 * the top most block, 1 the next block and so forth.
525
 */
526
527
GDALRasterBlock::GDALRasterBlock(GDALRasterBand *poBandIn, int nXOffIn,
528
                                 int nYOffIn)
529
0
    : eType(poBandIn->GetRasterDataType()), nXOff(nXOffIn), nYOff(nYOffIn),
530
0
      poBand(poBandIn), bMustDetach(true)
531
0
{
532
0
    if (!hRBLock)
533
0
    {
534
        // Needed for scenarios where GDALAllRegister() is called after
535
        // GDALDestroyDriverManager()
536
0
        INITIALIZE_LOCK;
537
0
    }
538
539
0
    CPLAssert(poBandIn != nullptr);
540
0
    poBand->GetBlockSize(&nXSize, &nYSize);
541
0
}
542
543
/************************************************************************/
544
/*                          GDALRasterBlock()                           */
545
/************************************************************************/
546
547
/**
548
 * @brief GDALRasterBlock Constructor (for GDALHashSetBandBlockAccess purpose)
549
 *
550
 * Normally only called from GDALHashSetBandBlockAccess class. Such a block
551
 * is completely non functional and only meant as being used to do a look-up
552
 * in the hash set of GDALHashSetBandBlockAccess
553
 *
554
 * @param nXOffIn the horizontal block offset, with zero indicating
555
 * the left most block, 1 the next block and so forth.
556
 *
557
 * @param nYOffIn the vertical block offset, with zero indicating
558
 * the top most block, 1 the next block and so forth.
559
 */
560
561
GDALRasterBlock::GDALRasterBlock(int nXOffIn, int nYOffIn)
562
0
    : nXOff(nXOffIn), nYOff(nYOffIn)
563
0
{
564
0
}
565
566
/************************************************************************/
567
/*                                  RecycleFor()                        */
568
/************************************************************************/
569
570
/**
571
 * Recycle an existing block (of the same band)
572
 *
573
 * Normally called from GDALAbstractBandBlockCache::CreateBlock().
574
 */
575
576
void GDALRasterBlock::RecycleFor(int nXOffIn, int nYOffIn)
577
0
{
578
0
    CPLAssert(pData == nullptr);
579
0
    pData = nullptr;
580
0
    bDirty = false;
581
0
    nLockCount = 0;
582
583
0
    poNext = nullptr;
584
0
    poPrevious = nullptr;
585
586
0
    nXOff = nXOffIn;
587
0
    nYOff = nYOffIn;
588
0
    bMustDetach = true;
589
0
}
590
591
/************************************************************************/
592
/*                          ~GDALRasterBlock()                          */
593
/************************************************************************/
594
595
/**
596
 * Block destructor.
597
 *
598
 * Normally called from GDALRasterBand::FlushBlock().
599
 */
600
601
GDALRasterBlock::~GDALRasterBlock()
602
603
0
{
604
0
    Detach();
605
606
0
    if (pData != nullptr)
607
0
    {
608
0
        VSIFreeAligned(pData);
609
0
    }
610
611
0
    CPLAssert(nLockCount <= 0);
612
613
#ifdef ENABLE_DEBUG
614
    Verify();
615
#endif
616
0
}
617
618
/************************************************************************/
619
/*                        GetEffectiveBlockSize()                       */
620
/************************************************************************/
621
622
static size_t GetEffectiveBlockSize(GPtrDiff_t nBlockSize)
623
0
{
624
    // The real cost of a block allocation is more than just nBlockSize
625
    // As we allocate with 64-byte alignment, use 64 as a multiple.
626
    // We arbitrarily add 2 * sizeof(GDALRasterBlock) to account for that
627
0
    return static_cast<size_t>(
628
0
        std::min(static_cast<GUIntBig>(UINT_MAX),
629
0
                 static_cast<GUIntBig>(DIV_ROUND_UP(nBlockSize, 64)) * 64 +
630
0
                     2 * sizeof(GDALRasterBlock)));
631
0
}
632
633
/************************************************************************/
634
/*                               Detach()                               */
635
/************************************************************************/
636
637
/**
638
 * Remove block from cache.
639
 *
640
 * This method removes the current block from the linked list used to keep
641
 * track of all cached blocks in order of age.  It does not affect whether
642
 * the block is referenced by a GDALRasterBand nor does it destroy or flush
643
 * the block.
644
 */
645
646
void GDALRasterBlock::Detach()
647
648
0
{
649
0
    if (bMustDetach)
650
0
    {
651
0
        TAKE_LOCK;
652
0
        Detach_unlocked();
653
0
    }
654
0
}
655
656
void GDALRasterBlock::Detach_unlocked()
657
0
{
658
0
    if (poOldest == this)
659
0
        poOldest = poPrevious;
660
661
0
    if (poNewest == this)
662
0
    {
663
0
        poNewest = poNext;
664
0
    }
665
666
0
    if (poPrevious != nullptr)
667
0
        poPrevious->poNext = poNext;
668
669
0
    if (poNext != nullptr)
670
0
        poNext->poPrevious = poPrevious;
671
672
0
    poPrevious = nullptr;
673
0
    poNext = nullptr;
674
0
    bMustDetach = false;
675
676
0
    if (pData)
677
0
        nCacheUsed -= GetEffectiveBlockSize(GetBlockSize());
678
679
#ifdef ENABLE_DEBUG
680
    Verify();
681
#endif
682
0
}
683
684
/************************************************************************/
685
/*                               Verify()                               */
686
/************************************************************************/
687
688
/**
689
 * Confirms (via assertions) that the block cache linked list is in a
690
 * consistent state.
691
 */
692
693
#ifdef ENABLE_DEBUG
694
void GDALRasterBlock::Verify()
695
696
{
697
    TAKE_LOCK;
698
699
    CPLAssert((poNewest == nullptr && poOldest == nullptr) ||
700
              (poNewest != nullptr && poOldest != nullptr));
701
702
    if (poNewest != nullptr)
703
    {
704
        CPLAssert(poNewest->poPrevious == nullptr);
705
        CPLAssert(poOldest->poNext == nullptr);
706
707
        GDALRasterBlock *poLast = nullptr;
708
        for (GDALRasterBlock *poBlock = poNewest; poBlock != nullptr;
709
             poBlock = poBlock->poNext)
710
        {
711
            CPLAssert(poBlock->poPrevious == poLast);
712
713
            poLast = poBlock;
714
        }
715
716
        CPLAssert(poOldest == poLast);
717
    }
718
}
719
720
#else
721
void GDALRasterBlock::Verify()
722
0
{
723
0
}
724
#endif
725
726
#ifdef notdef
727
void GDALRasterBlock::CheckNonOrphanedBlocks(GDALRasterBand *poBand)
728
{
729
    TAKE_LOCK;
730
    for (GDALRasterBlock *poBlock = poNewest; poBlock != nullptr;
731
         poBlock = poBlock->poNext)
732
    {
733
        if (poBlock->GetBand() == poBand)
734
        {
735
            printf("Cache has still blocks of band %p\n", poBand); /*ok*/
736
            printf("Band : %d\n", poBand->GetBand());              /*ok*/
737
            printf("nRasterXSize = %d\n", poBand->GetXSize());     /*ok*/
738
            printf("nRasterYSize = %d\n", poBand->GetYSize());     /*ok*/
739
            int nBlockXSize, nBlockYSize;
740
            poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
741
            printf("nBlockXSize = %d\n", nBlockXSize);      /*ok*/
742
            printf("nBlockYSize = %d\n", nBlockYSize);      /*ok*/
743
            printf("Dataset : %p\n", poBand->GetDataset()); /*ok*/
744
            if (poBand->GetDataset())
745
                printf("Dataset : %s\n", /*ok*/
746
                       poBand->GetDataset()->GetDescription());
747
        }
748
    }
749
}
750
#endif
751
752
/************************************************************************/
753
/*                               Write()                                */
754
/************************************************************************/
755
756
/**
757
 * Force writing of the current block, if dirty.
758
 *
759
 * The block is written using GDALRasterBand::IWriteBlock() on its
760
 * corresponding band object.  Even if the write fails the block will
761
 * be marked clean.
762
 *
763
 * @return CE_None otherwise the error returned by IWriteBlock().
764
 */
765
766
CPLErr GDALRasterBlock::Write()
767
768
0
{
769
0
    if (!GetDirty())
770
0
        return CE_None;
771
772
0
    if (poBand == nullptr)
773
0
        return CE_Failure;
774
775
0
    MarkClean();
776
777
0
    if (poBand->eFlushBlockErr == CE_None)
778
0
    {
779
0
        int bCallLeaveReadWrite = poBand->EnterReadWrite(GF_Write);
780
0
        CPLErr eErr = poBand->IWriteBlock(nXOff, nYOff, pData);
781
0
        if (bCallLeaveReadWrite)
782
0
            poBand->LeaveReadWrite();
783
0
        return eErr;
784
0
    }
785
0
    else
786
0
        return poBand->eFlushBlockErr;
787
0
}
788
789
/************************************************************************/
790
/*                               Touch()                                */
791
/************************************************************************/
792
793
/**
794
 * Push block to top of LRU (least-recently used) list.
795
 *
796
 * This method is normally called when a block is used to keep track
797
 * that it has been recently used.
798
 */
799
800
void GDALRasterBlock::Touch()
801
802
0
{
803
    // Can be safely tested outside the lock
804
0
    if (poNewest == this)
805
0
        return;
806
807
0
    TAKE_LOCK;
808
0
    Touch_unlocked();
809
0
}
810
811
void GDALRasterBlock::Touch_unlocked()
812
813
0
{
814
    // Could happen even if tested in Touch() before taking the lock
815
    // Scenario would be :
816
    // 0. this is the second block (the one pointed by poNewest->poNext)
817
    // 1. Thread 1 calls Touch() and poNewest != this at that point
818
    // 2. Thread 2 detaches poNewest
819
    // 3. Thread 1 arrives here
820
0
    if (poNewest == this)
821
0
        return;
822
823
    // We should not try to touch a block that has been detached.
824
    // If that happen, corruption has already occurred.
825
0
    CPLAssert(bMustDetach);
826
827
0
    if (poOldest == this)
828
0
        poOldest = this->poPrevious;
829
830
0
    if (poPrevious != nullptr)
831
0
        poPrevious->poNext = poNext;
832
833
0
    if (poNext != nullptr)
834
0
        poNext->poPrevious = poPrevious;
835
836
0
    poPrevious = nullptr;
837
0
    poNext = poNewest;
838
839
0
    if (poNewest != nullptr)
840
0
    {
841
0
        CPLAssert(poNewest->poPrevious == nullptr);
842
0
        poNewest->poPrevious = this;
843
0
    }
844
0
    poNewest = this;
845
846
0
    if (poOldest == nullptr)
847
0
    {
848
0
        CPLAssert(poPrevious == nullptr && poNext == nullptr);
849
0
        poOldest = this;
850
0
    }
851
#ifdef ENABLE_DEBUG
852
    Verify();
853
#endif
854
0
}
855
856
/************************************************************************/
857
/*                            Internalize()                             */
858
/************************************************************************/
859
860
/**
861
 * Allocate memory for block.
862
 *
863
 * This method allocates memory for the block, and attempts to flush other
864
 * blocks, if necessary, to bring the total cache size back within the limits.
865
 * The newly allocated block is touched and will be considered most recently
866
 * used in the LRU list.
867
 *
868
 * @return CE_None on success or CE_Failure if memory allocation fails.
869
 */
870
871
CPLErr GDALRasterBlock::Internalize()
872
873
0
{
874
0
    CPLAssert(pData == nullptr);
875
876
0
    void *pNewData = nullptr;
877
878
    // This call will initialize the hRBLock mutex. Other call places can
879
    // only be called if we have go through there.
880
0
    const GIntBig nCurCacheMax = GDALGetCacheMax64();
881
882
    // No risk of overflow as it is checked in GDALRasterBand::InitBlockInfo().
883
0
    const auto nSizeInBytes = GetBlockSize();
884
885
    /* -------------------------------------------------------------------- */
886
    /*      Flush old blocks if we are nearing our memory limit.            */
887
    /* -------------------------------------------------------------------- */
888
0
    bool bFirstIter = true;
889
0
    bool bLoopAgain = false;
890
0
    GDALDataset *poThisDS = poBand->GetDataset();
891
0
    do
892
0
    {
893
0
        bLoopAgain = false;
894
0
        GDALRasterBlock *apoBlocksToFree[64] = {nullptr};
895
0
        int nBlocksToFree = 0;
896
0
        {
897
0
            TAKE_LOCK;
898
899
0
            if (bFirstIter)
900
0
                nCacheUsed += GetEffectiveBlockSize(nSizeInBytes);
901
0
            GDALRasterBlock *poTarget = poOldest;
902
0
            while (nCacheUsed > nCurCacheMax)
903
0
            {
904
0
                GDALRasterBlock *poDirtyBlockOtherDataset = nullptr;
905
                // In this first pass, only discard dirty blocks of this
906
                // dataset. We do this to decrease significantly the likelihood
907
                // of the following weakness of the block cache design:
908
                // 1. Thread 1 fills block B with ones
909
                // 2. Thread 2 evicts this dirty block, while thread 1 almost
910
                //    at the same time (but slightly after) tries to reacquire
911
                //    this block. As it has been removed from the block cache
912
                //    array/set, thread 1 now tries to read block B from disk,
913
                //    so gets the old value.
914
0
                while (poTarget != nullptr)
915
0
                {
916
0
                    if (!poTarget->GetDirty())
917
0
                    {
918
0
                        if (CPLAtomicCompareAndExchange(&(poTarget->nLockCount),
919
0
                                                        0, -1))
920
0
                            break;
921
0
                    }
922
0
                    else if (nDisableDirtyBlockFlushCounter == 0)
923
0
                    {
924
0
                        if (poTarget->poBand->GetDataset() == poThisDS)
925
0
                        {
926
0
                            if (CPLAtomicCompareAndExchange(
927
0
                                    &(poTarget->nLockCount), 0, -1))
928
0
                                break;
929
0
                        }
930
0
                        else if (poDirtyBlockOtherDataset == nullptr)
931
0
                        {
932
0
                            poDirtyBlockOtherDataset = poTarget;
933
0
                        }
934
0
                    }
935
0
                    poTarget = poTarget->poPrevious;
936
0
                }
937
0
                if (poTarget == nullptr && poDirtyBlockOtherDataset)
938
0
                {
939
0
                    if (CPLAtomicCompareAndExchange(
940
0
                            &(poDirtyBlockOtherDataset->nLockCount), 0, -1))
941
0
                    {
942
0
                        CPLDebug("GDAL",
943
0
                                 "Evicting dirty block of another dataset");
944
0
                        poTarget = poDirtyBlockOtherDataset;
945
0
                    }
946
0
                    else
947
0
                    {
948
0
                        poTarget = poOldest;
949
0
                        while (poTarget != nullptr)
950
0
                        {
951
0
                            if (CPLAtomicCompareAndExchange(
952
0
                                    &(poTarget->nLockCount), 0, -1))
953
0
                            {
954
0
                                CPLDebug(
955
0
                                    "GDAL",
956
0
                                    "Evicting dirty block of another dataset");
957
0
                                break;
958
0
                            }
959
0
                            poTarget = poTarget->poPrevious;
960
0
                        }
961
0
                    }
962
0
                }
963
964
0
                if (poTarget != nullptr)
965
0
                {
966
0
#ifndef __COVERITY__
967
                    // Disabled to avoid complains about sleeping under locks,
968
                    // that are only true for debug/testing code
969
0
                    if (bSleepsForBockCacheDebug)
970
0
                    {
971
0
                        const double dfDelay = CPLAtof(CPLGetConfigOption(
972
0
                            "GDAL_RB_INTERNALIZE_SLEEP_AFTER_DROP_LOCK", "0"));
973
0
                        if (dfDelay > 0)
974
0
                            CPLSleep(dfDelay);
975
0
                    }
976
0
#endif
977
978
0
                    GDALRasterBlock *_poPrevious = poTarget->poPrevious;
979
980
0
                    poTarget->Detach_unlocked();
981
0
                    poTarget->GetBand()->UnreferenceBlock(poTarget);
982
983
0
                    apoBlocksToFree[nBlocksToFree++] = poTarget;
984
0
                    if (poTarget->GetDirty())
985
0
                    {
986
                        // Only free one dirty block at a time so that
987
                        // other dirty blocks of other bands with the same
988
                        // coordinates can be found with TryGetLockedBlock()
989
0
                        bLoopAgain = nCacheUsed > nCurCacheMax;
990
0
                        break;
991
0
                    }
992
0
                    if (nBlocksToFree == 64)
993
0
                    {
994
0
                        bLoopAgain = (nCacheUsed > nCurCacheMax);
995
0
                        break;
996
0
                    }
997
998
0
                    poTarget = _poPrevious;
999
0
                }
1000
0
                else
1001
0
                {
1002
0
                    break;
1003
0
                }
1004
0
            }
1005
1006
            /* ------------------------------------------------------------------
1007
             */
1008
            /*      Add this block to the list. */
1009
            /* ------------------------------------------------------------------
1010
             */
1011
0
            if (!bLoopAgain)
1012
0
                Touch_unlocked();
1013
0
        }
1014
1015
0
        bFirstIter = false;
1016
1017
        // Now free blocks we have detached and removed from their band.
1018
0
        for (int i = 0; i < nBlocksToFree; ++i)
1019
0
        {
1020
0
            GDALRasterBlock *const poBlock = apoBlocksToFree[i];
1021
1022
0
            if (poBlock->GetDirty())
1023
0
            {
1024
0
#ifndef __COVERITY__
1025
                // Disabled to avoid complains about sleeping under locks, that
1026
                // are only true for debug/testing code
1027
0
                if (bSleepsForBockCacheDebug)
1028
0
                {
1029
0
                    const double dfDelay = CPLAtof(CPLGetConfigOption(
1030
0
                        "GDAL_RB_INTERNALIZE_SLEEP_AFTER_DETACH_BEFORE_WRITE",
1031
0
                        "0"));
1032
0
                    if (dfDelay > 0)
1033
0
                        CPLSleep(dfDelay);
1034
0
                }
1035
0
#endif
1036
1037
0
                CPLErr eErr = poBlock->Write();
1038
0
                if (eErr != CE_None)
1039
0
                {
1040
                    // Save the error for later reporting.
1041
0
                    poBlock->GetBand()->SetFlushBlockErr(eErr);
1042
0
                }
1043
0
            }
1044
1045
            // Try to recycle the data of an existing block.
1046
0
            void *pDataBlock = poBlock->pData;
1047
0
            if (pNewData == nullptr && pDataBlock != nullptr &&
1048
0
                poBlock->GetBlockSize() == nSizeInBytes)
1049
0
            {
1050
0
                pNewData = pDataBlock;
1051
0
            }
1052
0
            else
1053
0
            {
1054
0
                VSIFreeAligned(poBlock->pData);
1055
0
            }
1056
0
            poBlock->pData = nullptr;
1057
1058
0
            poBlock->GetBand()->AddBlockToFreeList(poBlock);
1059
0
        }
1060
0
    } while (bLoopAgain);
1061
1062
0
    if (pNewData == nullptr)
1063
0
    {
1064
0
        pNewData = VSI_MALLOC_ALIGNED_AUTO_VERBOSE(nSizeInBytes);
1065
0
        if (pNewData == nullptr)
1066
0
        {
1067
0
            return (CE_Failure);
1068
0
        }
1069
0
    }
1070
1071
0
    pData = pNewData;
1072
1073
0
    return CE_None;
1074
0
}
1075
1076
/************************************************************************/
1077
/*                             MarkDirty()                              */
1078
/************************************************************************/
1079
1080
/**
1081
 * Mark the block as modified.
1082
 *
1083
 * A dirty block is one that has been modified and will need to be written
1084
 * to disk before it can be flushed.
1085
 */
1086
1087
void GDALRasterBlock::MarkDirty()
1088
0
{
1089
0
    if (poBand)
1090
0
    {
1091
0
        poBand->InitRWLock();
1092
0
        if (!bDirty)
1093
0
            poBand->IncDirtyBlocks(1);
1094
0
    }
1095
0
    bDirty = true;
1096
0
}
1097
1098
/************************************************************************/
1099
/*                             MarkClean()                              */
1100
/************************************************************************/
1101
1102
/**
1103
 * Mark the block as unmodified.
1104
 *
1105
 * A dirty block is one that has been modified and will need to be written
1106
 * to disk before it can be flushed.
1107
 */
1108
1109
void GDALRasterBlock::MarkClean()
1110
0
{
1111
0
    if (bDirty && poBand)
1112
0
        poBand->IncDirtyBlocks(-1);
1113
0
    bDirty = false;
1114
0
}
1115
1116
/************************************************************************/
1117
/*                          DestroyRBMutex()                           */
1118
/************************************************************************/
1119
1120
/*! @cond Doxygen_Suppress */
1121
void GDALRasterBlock::DestroyRBMutex()
1122
0
{
1123
0
    if (hRBLock != nullptr)
1124
0
        DESTROY_LOCK;
1125
0
    hRBLock = nullptr;
1126
0
}
1127
1128
/*! @endcond */
1129
1130
/************************************************************************/
1131
/*                              TakeLock()                              */
1132
/************************************************************************/
1133
1134
/**
1135
 * Take a lock and Touch().
1136
 *
1137
 * Should only be used by GDALArrayBandBlockCache::TryGetLockedBlockRef()
1138
 * and GDALHashSetBandBlockCache::TryGetLockedBlockRef()
1139
 *
1140
 * @return TRUE if the lock has been successfully acquired. If FALSE, the
1141
 *         block is being evicted by another thread, and so should be
1142
 *         considered as invalid.
1143
 */
1144
1145
int GDALRasterBlock::TakeLock()
1146
0
{
1147
0
    const int nLockVal = AddLock();
1148
0
    CPLAssert(nLockVal >= 0);
1149
0
#ifndef __COVERITY__
1150
    // Disabled to avoid complains about sleeping under locks, that
1151
    // are only true for debug/testing code
1152
0
    if (bSleepsForBockCacheDebug)
1153
0
    {
1154
0
        const double dfDelay = CPLAtof(
1155
0
            CPLGetConfigOption("GDAL_RB_TRYGET_SLEEP_AFTER_TAKE_LOCK", "0"));
1156
0
        if (dfDelay > 0)
1157
0
            CPLSleep(dfDelay);
1158
0
    }
1159
0
#endif
1160
1161
0
    if (nLockVal == 0)
1162
0
    {
1163
        // The block is being evicted by GDALRasterBlock::Internalize()
1164
        // or FlushCacheBlock()
1165
1166
0
#ifdef DEBUG
1167
0
        CPLDebug(
1168
0
            "GDAL",
1169
0
            "TakeLock(%p): Block(%d,%d,%p) is being evicted while trying to "
1170
0
            "reacquire it.",
1171
0
            reinterpret_cast<void *>(CPLGetPID()), nXOff, nYOff, poBand);
1172
0
#endif
1173
0
        DropLock();
1174
1175
0
        return FALSE;
1176
0
    }
1177
0
    Touch();
1178
0
    return TRUE;
1179
0
}
1180
1181
/************************************************************************/
1182
/*                      DropLockForRemovalFromStorage()                 */
1183
/************************************************************************/
1184
1185
/**
1186
 * Drop a lock before removing the block from the band storage.
1187
 *
1188
 * Should only be used by GDALArrayBandBlockCache::FlushBlock()
1189
 * and GDALHashSetBandBlockCache::FlushBlock()
1190
 *
1191
 * @return TRUE if the lock has been successfully dropped.
1192
 */
1193
1194
int GDALRasterBlock::DropLockForRemovalFromStorage()
1195
0
{
1196
    // Detect potential conflict with GDALRasterBlock::Internalize()
1197
    // or FlushCacheBlock()
1198
0
    if (CPLAtomicCompareAndExchange(&nLockCount, 0, -1))
1199
0
        return TRUE;
1200
0
#ifdef DEBUG
1201
0
    CPLDebug("GDAL",
1202
0
             "DropLockForRemovalFromStorage(%p): Block(%d,%d,%p) was attempted "
1203
0
             "to be flushed from band but it is flushed by global cache.",
1204
0
             reinterpret_cast<void *>(CPLGetPID()), nXOff, nYOff, poBand);
1205
0
#endif
1206
1207
    // Wait for the block for having been unreferenced.
1208
0
    TAKE_LOCK;
1209
1210
0
    return FALSE;
1211
0
}
1212
1213
#if 0
1214
void GDALRasterBlock::DumpAll()
1215
{
1216
    int iBlock = 0;
1217
    for( GDALRasterBlock *poBlock = poNewest;
1218
         poBlock != nullptr;
1219
         poBlock = poBlock->poNext )
1220
    {
1221
        printf("Block %d\n", iBlock);/*ok*/
1222
        poBlock->DumpBlock();
1223
        printf("\n");/*ok*/
1224
        iBlock++;
1225
    }
1226
}
1227
1228
void GDALRasterBlock::DumpBlock()
1229
{
1230
    printf("  Lock count = %d\n", nLockCount);/*ok*/
1231
    printf("  bDirty = %d\n", static_cast<int>(bDirty));/*ok*/
1232
    printf("  nXOff = %d\n", nXOff);/*ok*/
1233
    printf("  nYOff = %d\n", nYOff);/*ok*/
1234
    printf("  nXSize = %d\n", nXSize);/*ok*/
1235
    printf("  nYSize = %d\n", nYSize);/*ok*/
1236
    printf("  eType = %d\n", eType);/*ok*/
1237
    printf("  Band %p\n", GetBand());/*ok*/
1238
    printf("  Band %d\n", GetBand()->GetBand());/*ok*/
1239
    if( GetBand()->GetDataset() )
1240
        printf("  Dataset = %s\n",/*ok*/
1241
               GetBand()->GetDataset()->GetDescription());
1242
}
1243
#endif  // if 0