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