Coverage Report

Created: 2025-06-13 06:29

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