Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Core
4 : * Purpose: Test block cache under multi-threading
5 : * Author: Even Rouault, <even dot rouault at spatialys dot com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2015, Even Rouault <even dot rouault at spatialys dot com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #ifndef DEBUG
14 : #define DEBUG
15 : #endif
16 :
17 : #include "cpl_multiproc.h"
18 : #include "gdal_priv.h"
19 :
20 : #include <cstdlib>
21 : #include <vector>
22 :
23 : #include "gtest_include.h"
24 :
25 : extern int global_argc;
26 : extern char **global_argv;
27 :
28 : namespace
29 : {
30 :
31 : CPLLock *psLock = nullptr;
32 :
33 0 : static void Usage()
34 : {
35 0 : printf("Usage: testblockcache [-threads X] [-loops X] [-max_requests X] "
36 : "[-strategy random|line|block]\n");
37 0 : printf(" [-migrate] [ filename |\n");
38 0 : printf(" [[-xsize val] [-ysize val] [-bands val] "
39 : "[-co key=value]*\n");
40 0 : printf(" [[-memdriver] | [-ondisk]] [-check]] ]\n");
41 0 : exit(1);
42 : }
43 :
44 : int nLoops = 1;
45 : const char *pszDataset = nullptr;
46 : int bCheck = FALSE;
47 :
48 : typedef enum
49 : {
50 : STRATEGY_RANDOM,
51 : STRATEGY_LINE,
52 : STRATEGY_BLOCK,
53 : } Strategy;
54 :
55 : typedef struct _Request Request;
56 :
57 : struct _Request
58 : {
59 : int nXOff, nYOff, nXWin, nYWin;
60 : int nBands;
61 : Request *psNext;
62 : };
63 :
64 : typedef struct _Resource Resource;
65 :
66 : struct _Resource
67 : {
68 : GDALDataset *poDS;
69 : void *pBuffer;
70 : Resource *psNext;
71 : Resource *psPrev;
72 : };
73 :
74 : typedef struct
75 : {
76 : GDALDataset *poDS;
77 : Request *psRequestList;
78 : int nBufferSize;
79 : } ThreadDescription;
80 :
81 : static Request *psGlobalRequestList = nullptr;
82 : static Resource *psGlobalResourceList = nullptr;
83 : static Resource *psGlobalResourceLast = nullptr;
84 :
85 : /* according to rand() man page, POSIX.1-2001 proposes the following
86 : * implementation */
87 : /* RAND_MAX assumed to be 32767 */
88 : #define MYRAND_MAX 32767
89 :
90 5459570000 : static int myrand_r(unsigned long *pseed)
91 : {
92 5459570000 : *pseed = *pseed * 1103515245 + 12345;
93 5459570000 : return ((unsigned)((*pseed / 65536UL) % (MYRAND_MAX + 1)));
94 : }
95 :
96 22400 : static void Check(GByte *pBuffer, int nXSize, int nYSize, int nBands, int nXOff,
97 : int nYOff, int nXWin, int nYWin)
98 : {
99 112000 : for (int iBand = 0; iBand < nBands; iBand++)
100 : {
101 21674500 : for (int iY = 0; iY < nYWin; iY++)
102 : {
103 5027060000 : for (int iX = 0; iX < nXWin; iX++)
104 : {
105 5005480000 : unsigned long seed = iBand * nXSize * nYSize +
106 5005480000 : (iY + nYOff) * nXSize + iX + nXOff;
107 5005480000 : GByte expected = (GByte)(myrand_r(&seed) & 0xff);
108 5240460000 : EXPECT_EQ(pBuffer[iBand * nXWin * nYWin + iY * nXWin + iX],
109 : expected);
110 : }
111 : }
112 : }
113 22400 : }
114 :
115 22400 : static void ReadRaster(GDALDataset *poDS, int nXSize, int nYSize, int nBands,
116 : GByte *pBuffer, int nXOff, int nYOff, int nXWin,
117 : int nYWin)
118 : {
119 22400 : CPL_IGNORE_RET_VAL(poDS->RasterIO(GF_Read, nXOff, nYOff, nXWin, nYWin,
120 : pBuffer, nXWin, nYWin, GDT_Byte, nBands,
121 : nullptr, 0, 0, 0
122 : #ifdef GDAL_COMPILATION
123 : ,
124 : nullptr
125 : #endif
126 : ));
127 22400 : if (bCheck)
128 : {
129 22400 : Check(pBuffer, nXSize, nYSize, nBands, nXOff, nYOff, nXWin, nYWin);
130 : }
131 22400 : }
132 :
133 22400 : static void AddRequest(Request *&psRequestList, Request *&psRequestLast,
134 : int nXOff, int nYOff, int nXWin, int nYWin, int nBands)
135 : {
136 22400 : Request *psRequest = (Request *)CPLMalloc(sizeof(Request));
137 22400 : psRequest->nXOff = nXOff;
138 22400 : psRequest->nYOff = nYOff;
139 22400 : psRequest->nXWin = nXWin;
140 22400 : psRequest->nYWin = nYWin;
141 22400 : psRequest->nBands = nBands;
142 22400 : if (psRequestLast)
143 22379 : psRequestLast->psNext = psRequest;
144 : else
145 21 : psRequestList = psRequest;
146 22400 : psRequestLast = psRequest;
147 22400 : psRequest->psNext = nullptr;
148 22400 : }
149 :
150 22404 : static Request *GetNextRequest(Request *&psRequestList)
151 : {
152 22404 : if (psLock)
153 1604 : CPLAcquireLock(psLock);
154 22404 : Request *psRet = psRequestList;
155 22404 : if (psRequestList)
156 : {
157 22400 : psRequestList = psRequestList->psNext;
158 22400 : psRet->psNext = nullptr;
159 : }
160 22404 : if (psLock)
161 1604 : CPLReleaseLock(psLock);
162 22404 : return psRet;
163 : }
164 :
165 1600 : static Resource *AcquireFirstResource()
166 : {
167 1600 : if (psLock)
168 1600 : CPLAcquireLock(psLock);
169 1600 : Resource *psRet = psGlobalResourceList;
170 1600 : psGlobalResourceList = psGlobalResourceList->psNext;
171 1600 : if (psGlobalResourceList)
172 3 : psGlobalResourceList->psPrev = nullptr;
173 : else
174 1597 : psGlobalResourceLast = nullptr;
175 1600 : psRet->psNext = nullptr;
176 1600 : CPLAssert(psRet->psPrev == nullptr);
177 1600 : if (psLock)
178 1600 : CPLReleaseLock(psLock);
179 1600 : return psRet;
180 : }
181 :
182 1604 : static void PutResourceAtEnd(Resource *psResource)
183 : {
184 1604 : if (psLock)
185 1600 : CPLAcquireLock(psLock);
186 1604 : psResource->psPrev = psGlobalResourceLast;
187 1604 : psResource->psNext = nullptr;
188 1604 : if (psGlobalResourceList == nullptr)
189 1598 : psGlobalResourceList = psResource;
190 : else
191 6 : psGlobalResourceLast->psNext = psResource;
192 1604 : psGlobalResourceLast = psResource;
193 1604 : if (psLock)
194 1600 : CPLReleaseLock(psLock);
195 1604 : }
196 :
197 20 : static void ThreadFuncDedicatedDataset(void *_psThreadDescription)
198 : {
199 20 : ThreadDescription *psThreadDescription =
200 : (ThreadDescription *)_psThreadDescription;
201 20 : int nXSize = psThreadDescription->poDS->GetRasterXSize();
202 20 : int nYSize = psThreadDescription->poDS->GetRasterYSize();
203 20 : void *pBuffer = CPLMalloc(psThreadDescription->nBufferSize);
204 20820 : while (psThreadDescription->psRequestList != nullptr)
205 : {
206 20800 : Request *psRequest = GetNextRequest(psThreadDescription->psRequestList);
207 20800 : ReadRaster(psThreadDescription->poDS, nXSize, nYSize, psRequest->nBands,
208 : (GByte *)pBuffer, psRequest->nXOff, psRequest->nYOff,
209 : psRequest->nXWin, psRequest->nYWin);
210 20800 : CPLFree(psRequest);
211 : }
212 20 : CPLFree(pBuffer);
213 20 : }
214 :
215 1604 : static void ThreadFuncWithMigration(void * /* _unused */)
216 : {
217 : Request *psRequest;
218 1604 : while ((psRequest = GetNextRequest(psGlobalRequestList)) != nullptr)
219 : {
220 1600 : Resource *psResource = AcquireFirstResource();
221 1600 : ASSERT_TRUE(psResource != nullptr);
222 1600 : int nXSize = psResource->poDS->GetRasterXSize();
223 1600 : int nYSize = psResource->poDS->GetRasterYSize();
224 1600 : ReadRaster(psResource->poDS, nXSize, nYSize, psRequest->nBands,
225 1600 : (GByte *)psResource->pBuffer, psRequest->nXOff,
226 : psRequest->nYOff, psRequest->nXWin, psRequest->nYWin);
227 1600 : CPLFree(psRequest);
228 1600 : PutResourceAtEnd(psResource);
229 : }
230 : }
231 :
232 24 : static int CreateRandomStrategyRequests(GDALDataset *poDS, int nMaxRequests,
233 : Request *&psRequestList,
234 : Request *&psRequestLast)
235 : {
236 24 : unsigned long seed = 1;
237 24 : int nXSize = poDS->GetRasterXSize();
238 24 : int nYSize = poDS->GetRasterYSize();
239 24 : int nMaxXWin = MIN(1000, nXSize / 10 + 1);
240 24 : int nMaxYWin = MIN(1000, nYSize / 10 + 1);
241 24 : int nQueriedBands = MIN(4, poDS->GetRasterCount());
242 24 : int nAverageIterationsToReadWholeFile =
243 24 : ((nXSize + nMaxXWin / 2 - 1) / (nMaxXWin / 2)) *
244 24 : ((nYSize + nMaxYWin / 2 - 1) / (nMaxYWin / 2));
245 24 : int nLocalLoops = nLoops * nAverageIterationsToReadWholeFile;
246 22424 : for (int iLoop = 0; iLoop < nLocalLoops; iLoop++)
247 : {
248 22400 : if (nMaxRequests > 0 && iLoop == nMaxRequests)
249 0 : break;
250 22400 : int nXOff = (int)((GIntBig)myrand_r(&seed) * (nXSize - 1) / MYRAND_MAX);
251 22400 : int nYOff = (int)((GIntBig)myrand_r(&seed) * (nYSize - 1) / MYRAND_MAX);
252 22400 : int nXWin = 1 + (int)((GIntBig)myrand_r(&seed) * nMaxXWin / MYRAND_MAX);
253 22400 : int nYWin = 1 + (int)((GIntBig)myrand_r(&seed) * nMaxYWin / MYRAND_MAX);
254 22400 : if (nXOff + nXWin > nXSize)
255 1200 : nXWin = nXSize - nXOff;
256 22400 : if (nYOff + nYWin > nYSize)
257 1184 : nYWin = nYSize - nYOff;
258 22400 : AddRequest(psRequestList, psRequestLast, nXOff, nYOff, nXWin, nYWin,
259 : nQueriedBands);
260 : }
261 24 : return nQueriedBands * nMaxXWin * nMaxYWin;
262 : }
263 :
264 0 : static int CreateLineStrategyRequests(GDALDataset *poDS, int nMaxRequests,
265 : Request *&psRequestList,
266 : Request *&psRequestLast)
267 : {
268 0 : int nXSize = poDS->GetRasterXSize();
269 0 : int nYSize = poDS->GetRasterYSize();
270 0 : int nQueriedBands = MIN(4, poDS->GetRasterCount());
271 0 : int bStop = FALSE;
272 0 : int nRequests = 0;
273 0 : for (int iLoop = 0; !bStop && iLoop < nLoops; iLoop++)
274 : {
275 0 : for (int nYOff = 0; nYOff < nYSize; nYOff++)
276 : {
277 0 : if (nMaxRequests > 0 && nRequests == nMaxRequests)
278 : {
279 0 : bStop = TRUE;
280 0 : break;
281 : }
282 0 : AddRequest(psRequestList, psRequestLast, 0, nYOff, nXSize, 1,
283 : nQueriedBands);
284 0 : nRequests++;
285 : }
286 : }
287 0 : return nQueriedBands * nXSize;
288 : }
289 :
290 0 : static int CreateBlockStrategyRequests(GDALDataset *poDS, int nMaxRequests,
291 : Request *&psRequestList,
292 : Request *&psRequestLast)
293 : {
294 0 : int nXSize = poDS->GetRasterXSize();
295 0 : int nYSize = poDS->GetRasterYSize();
296 0 : int nMaxXWin = MIN(1000, nXSize / 10 + 1);
297 0 : int nMaxYWin = MIN(1000, nYSize / 10 + 1);
298 0 : int nQueriedBands = MIN(4, poDS->GetRasterCount());
299 0 : int bStop = FALSE;
300 0 : int nRequests = 0;
301 0 : for (int iLoop = 0; !bStop && iLoop < nLoops; iLoop++)
302 : {
303 0 : for (int nYOff = 0; !bStop && nYOff < nYSize; nYOff += nMaxYWin)
304 : {
305 0 : int nReqYSize =
306 0 : (nYOff + nMaxYWin > nYSize) ? nYSize - nYOff : nMaxYWin;
307 0 : for (int nXOff = 0; nXOff < nXSize; nXOff += nMaxXWin)
308 : {
309 0 : if (nMaxRequests > 0 && nRequests == nMaxRequests)
310 : {
311 0 : bStop = TRUE;
312 0 : break;
313 : }
314 0 : int nReqXSize =
315 0 : (nXOff + nMaxXWin > nXSize) ? nXSize - nXOff : nMaxXWin;
316 0 : AddRequest(psRequestList, psRequestLast, nXOff, nYOff,
317 : nReqXSize, nReqYSize, nQueriedBands);
318 0 : nRequests++;
319 : }
320 : }
321 : }
322 0 : return nQueriedBands * nMaxXWin * nMaxYWin;
323 : }
324 :
325 24 : TEST(testblockcache, test)
326 : {
327 : int i;
328 6 : int nThreads = CPLGetNumCPUs();
329 12 : std::vector<CPLJoinableThread *> apsThreads;
330 6 : Strategy eStrategy = STRATEGY_RANDOM;
331 6 : int bNewDatasetOption = FALSE;
332 6 : int nXSize = 5000;
333 6 : int nYSize = 5000;
334 6 : int nBands = 4;
335 6 : char **papszOptions = nullptr;
336 6 : int bOnDisk = FALSE;
337 12 : std::vector<ThreadDescription> asThreadDescription;
338 6 : int bMemDriver = FALSE;
339 6 : GDALDataset *poMEMDS = nullptr;
340 6 : int bMigrate = FALSE;
341 6 : int nMaxRequests = -1;
342 :
343 6 : GDALAllRegister();
344 :
345 6 : int argc = global_argc;
346 6 : char **argv = global_argv;
347 23 : for (i = 1; i < argc; i++)
348 : {
349 17 : if (EQUAL(argv[i], "-threads") && i + 1 < argc)
350 : {
351 0 : i++;
352 0 : nThreads = atoi(argv[i]);
353 : }
354 17 : else if (EQUAL(argv[i], "-loops") && i + 1 < argc)
355 : {
356 4 : i++;
357 4 : nLoops = atoi(argv[i]);
358 4 : if (nLoops <= 0)
359 0 : nLoops = INT_MAX;
360 : }
361 13 : else if (EQUAL(argv[i], "-max_requests") && i + 1 < argc)
362 : {
363 0 : i++;
364 0 : nMaxRequests = atoi(argv[i]);
365 : }
366 13 : else if (EQUAL(argv[i], "-strategy") && i + 1 < argc)
367 : {
368 0 : i++;
369 0 : if (EQUAL(argv[i], "random"))
370 0 : eStrategy = STRATEGY_RANDOM;
371 0 : else if (EQUAL(argv[i], "line"))
372 0 : eStrategy = STRATEGY_LINE;
373 0 : else if (EQUAL(argv[i], "block"))
374 0 : eStrategy = STRATEGY_BLOCK;
375 : else
376 0 : Usage();
377 : }
378 13 : else if (EQUAL(argv[i], "-xsize") && i + 1 < argc)
379 : {
380 0 : i++;
381 0 : nXSize = atoi(argv[i]);
382 0 : bNewDatasetOption = TRUE;
383 : }
384 13 : else if (EQUAL(argv[i], "-ysize") && i + 1 < argc)
385 : {
386 0 : i++;
387 0 : nYSize = atoi(argv[i]);
388 0 : bNewDatasetOption = TRUE;
389 : }
390 13 : else if (EQUAL(argv[i], "-bands") && i + 1 < argc)
391 : {
392 0 : i++;
393 0 : nBands = atoi(argv[i]);
394 0 : bNewDatasetOption = TRUE;
395 : }
396 13 : else if (EQUAL(argv[i], "-co") && i + 1 < argc)
397 : {
398 5 : i++;
399 5 : papszOptions = CSLAddString(papszOptions, argv[i]);
400 5 : bNewDatasetOption = TRUE;
401 : }
402 8 : else if (EQUAL(argv[i], "-ondisk"))
403 : {
404 0 : bOnDisk = TRUE;
405 0 : bNewDatasetOption = TRUE;
406 : }
407 8 : else if (EQUAL(argv[i], "-check"))
408 : {
409 6 : bCheck = TRUE;
410 6 : bNewDatasetOption = TRUE;
411 : }
412 2 : else if (EQUAL(argv[i], "-memdriver"))
413 : {
414 1 : bMemDriver = TRUE;
415 1 : bNewDatasetOption = TRUE;
416 : }
417 1 : else if (EQUAL(argv[i], "-migrate"))
418 1 : bMigrate = TRUE;
419 0 : else if (argv[i][0] == '-')
420 0 : Usage();
421 0 : else if (pszDataset == nullptr)
422 0 : pszDataset = argv[i];
423 : else
424 : {
425 0 : Usage();
426 : }
427 : }
428 :
429 6 : if (pszDataset != nullptr && bNewDatasetOption)
430 0 : Usage();
431 :
432 6 : CPLDebug("TEST", "Using %d threads", nThreads);
433 :
434 6 : int bCreatedDataset = FALSE;
435 6 : if (pszDataset == nullptr)
436 : {
437 6 : bCreatedDataset = TRUE;
438 6 : if (bOnDisk)
439 0 : pszDataset = "/tmp/tmp.tif";
440 : else
441 6 : pszDataset = "/vsimem/tmp.tif";
442 : GDALDataset *poDS =
443 6 : ((GDALDriver *)GDALGetDriverByName((bMemDriver) ? "MEM" : "GTiff"))
444 6 : ->Create(pszDataset, nXSize, nYSize, nBands, GDT_Byte,
445 : papszOptions);
446 6 : if (bCheck)
447 : {
448 : GByte *pabyLine =
449 6 : (GByte *)VSIMalloc(static_cast<size_t>(nBands) * nXSize);
450 30006 : for (int iY = 0; iY < nYSize; iY++)
451 : {
452 150030000 : for (int iX = 0; iX < nXSize; iX++)
453 : {
454 750000000 : for (int iBand = 0; iBand < nBands; iBand++)
455 : {
456 600000000 : unsigned long seed =
457 600000000 : iBand * nXSize * nYSize + iY * nXSize + iX;
458 600000000 : pabyLine[iBand * nXSize + iX] =
459 600000000 : (GByte)(myrand_r(&seed) & 0xff);
460 : }
461 : }
462 30000 : CPL_IGNORE_RET_VAL(poDS->RasterIO(GF_Write, 0, iY, nXSize, 1,
463 : pabyLine, nXSize, 1, GDT_Byte,
464 : nBands, nullptr, 0, 0, 0
465 : #ifdef GDAL_COMPILATION
466 : ,
467 : nullptr
468 : #endif
469 : ));
470 : }
471 6 : VSIFree(pabyLine);
472 : }
473 6 : if (bMemDriver)
474 1 : poMEMDS = poDS;
475 : else
476 5 : GDALClose(poDS);
477 : }
478 : else
479 : {
480 0 : bCheck = FALSE;
481 : }
482 6 : CSLDestroy(papszOptions);
483 6 : papszOptions = nullptr;
484 :
485 6 : Request *psGlobalRequestLast = nullptr;
486 :
487 30 : for (i = 0; i < nThreads; i++)
488 : {
489 : GDALDataset *poDS;
490 : // Since GDAL 2.0, the MEM driver is thread-safe, i.e. does not use the
491 : // block cache, but only for operations not involving resampling, which
492 : // is the case here
493 24 : if (poMEMDS)
494 4 : poDS = poMEMDS;
495 : else
496 : {
497 20 : poDS = (GDALDataset *)GDALOpen(pszDataset, GA_ReadOnly);
498 20 : if (poDS == nullptr)
499 0 : exit(1);
500 : }
501 24 : if (bMigrate)
502 : {
503 4 : Resource *psResource = (Resource *)CPLMalloc(sizeof(Resource));
504 4 : psResource->poDS = poDS;
505 : int nBufferSize;
506 4 : if (eStrategy == STRATEGY_RANDOM)
507 4 : nBufferSize = CreateRandomStrategyRequests(poDS, nMaxRequests,
508 : psGlobalRequestList,
509 : psGlobalRequestLast);
510 0 : else if (eStrategy == STRATEGY_LINE)
511 0 : nBufferSize = CreateLineStrategyRequests(poDS, nMaxRequests,
512 : psGlobalRequestList,
513 : psGlobalRequestLast);
514 : else
515 0 : nBufferSize = CreateBlockStrategyRequests(poDS, nMaxRequests,
516 : psGlobalRequestList,
517 : psGlobalRequestLast);
518 4 : psResource->pBuffer = CPLMalloc(nBufferSize);
519 4 : PutResourceAtEnd(psResource);
520 : }
521 : else
522 : {
523 : ThreadDescription sThreadDescription;
524 20 : sThreadDescription.poDS = poDS;
525 20 : sThreadDescription.psRequestList = nullptr;
526 20 : Request *psRequestLast = nullptr;
527 20 : if (eStrategy == STRATEGY_RANDOM)
528 20 : sThreadDescription.nBufferSize = CreateRandomStrategyRequests(
529 : poDS, nMaxRequests, sThreadDescription.psRequestList,
530 : psRequestLast);
531 0 : else if (eStrategy == STRATEGY_LINE)
532 0 : sThreadDescription.nBufferSize = CreateLineStrategyRequests(
533 : poDS, nMaxRequests, sThreadDescription.psRequestList,
534 : psRequestLast);
535 : else
536 0 : sThreadDescription.nBufferSize = CreateBlockStrategyRequests(
537 : poDS, nMaxRequests, sThreadDescription.psRequestList,
538 : psRequestLast);
539 20 : asThreadDescription.push_back(sThreadDescription);
540 : }
541 : }
542 :
543 6 : if (bCreatedDataset && poMEMDS == nullptr && bOnDisk)
544 : {
545 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
546 0 : VSIUnlink(pszDataset);
547 0 : CPLPopErrorHandler();
548 : }
549 :
550 6 : if (bMigrate)
551 : {
552 1 : psLock = CPLCreateLock(LOCK_SPIN);
553 : }
554 :
555 30 : for (i = 0; i < nThreads; i++)
556 : {
557 : CPLJoinableThread *pThread;
558 24 : if (bMigrate)
559 4 : pThread = CPLCreateJoinableThread(ThreadFuncWithMigration, nullptr);
560 : else
561 20 : pThread = CPLCreateJoinableThread(ThreadFuncDedicatedDataset,
562 20 : &(asThreadDescription[i]));
563 24 : apsThreads.push_back(pThread);
564 : }
565 30 : for (i = 0; i < nThreads; i++)
566 : {
567 24 : CPLJoinThread(apsThreads[i]);
568 24 : if (!bMigrate && poMEMDS == nullptr)
569 16 : GDALClose(asThreadDescription[i].poDS);
570 : }
571 10 : while (psGlobalResourceList != nullptr)
572 : {
573 4 : CPLFree(psGlobalResourceList->pBuffer);
574 4 : if (poMEMDS == nullptr)
575 4 : GDALClose(psGlobalResourceList->poDS);
576 4 : Resource *psNext = psGlobalResourceList->psNext;
577 4 : CPLFree(psGlobalResourceList);
578 4 : psGlobalResourceList = psNext;
579 : }
580 :
581 6 : if (psLock)
582 : {
583 1 : CPLDestroyLock(psLock);
584 : }
585 :
586 6 : if (bCreatedDataset && poMEMDS == nullptr)
587 : {
588 5 : CPLPushErrorHandler(CPLQuietErrorHandler);
589 5 : VSIUnlink(pszDataset);
590 5 : CPLPopErrorHandler();
591 : }
592 6 : if (poMEMDS)
593 1 : GDALClose(poMEMDS);
594 :
595 6 : EXPECT_EQ(GDALGetCacheUsed64(), 0);
596 :
597 6 : GDALDestroyDriverManager();
598 6 : }
599 :
600 : } // namespace
|