1 : /******************************************************************************
2 : * $Id: gdalrasterblock.cpp 18501 2010-01-09 18:43:12Z mloskot $
3 : *
4 : * Project: GDAL Core
5 : * Purpose: Implementation of GDALRasterBlock class and related global
6 : * raster block cache management.
7 : * Author: Frank Warmerdam, warmerdam@pobox.com
8 : *
9 : **********************************************************************
10 : * Copyright (c) 1998, Frank Warmerdam <warmerdam@pobox.com>
11 : *
12 : * Permission is hereby granted, free of charge, to any person obtaining a
13 : * copy of this software and associated documentation files (the "Software"),
14 : * to deal in the Software without restriction, including without limitation
15 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 : * and/or sell copies of the Software, and to permit persons to whom the
17 : * Software is furnished to do so, subject to the following conditions:
18 : *
19 : * The above copyright notice and this permission notice shall be included
20 : * in all copies or substantial portions of the Software.
21 : *
22 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 : * DEALINGS IN THE SOFTWARE.
29 : ****************************************************************************/
30 :
31 : #include "gdal_priv.h"
32 : #include "cpl_multiproc.h"
33 :
34 : CPL_CVSID("$Id: gdalrasterblock.cpp 18501 2010-01-09 18:43:12Z mloskot $");
35 :
36 : static int bCacheMaxInitialized = FALSE;
37 : static int nCacheMax = 40 * 1024*1024;
38 : static volatile int nCacheUsed = 0;
39 :
40 : static volatile GDALRasterBlock *poOldest = NULL; /* tail */
41 : static volatile GDALRasterBlock *poNewest = NULL; /* head */
42 :
43 : static void *hRBMutex = NULL;
44 :
45 :
46 : /************************************************************************/
47 : /* GDALSetCacheMax() */
48 : /************************************************************************/
49 :
50 : /**
51 : * \brief Set maximum cache memory.
52 : *
53 : * This function sets the maximum amount of memory that GDAL is permitted
54 : * to use for GDALRasterBlock caching.
55 : *
56 : * @param nNewSize the maximum number of bytes for caching. Maximum is 2GB.
57 : */
58 :
59 6 : void CPL_STDCALL GDALSetCacheMax( int nNewSize )
60 :
61 : {
62 6 : nCacheMax = nNewSize;
63 :
64 : /* -------------------------------------------------------------------- */
65 : /* Flush blocks till we are under the new limit or till we */
66 : /* can't seem to flush anymore. */
67 : /* -------------------------------------------------------------------- */
68 12 : while( nCacheUsed > nCacheMax )
69 : {
70 0 : int nOldCacheUsed = nCacheUsed;
71 :
72 0 : GDALFlushCacheBlock();
73 :
74 0 : if( nCacheUsed == nOldCacheUsed )
75 0 : break;
76 : }
77 6 : }
78 :
79 : /************************************************************************/
80 : /* GDALGetCacheMax() */
81 : /************************************************************************/
82 :
83 : /**
84 : * \brief Get maximum cache memory.
85 : *
86 : * Gets the maximum amount of memory available to the GDALRasterBlock
87 : * caching system for caching GDAL read/write imagery.
88 : *
89 : * @return maximum in bytes.
90 : */
91 :
92 270277 : int CPL_STDCALL GDALGetCacheMax()
93 : {
94 270277 : if( !bCacheMaxInitialized )
95 : {
96 287 : if( CPLGetConfigOption("GDAL_CACHEMAX",NULL) != NULL )
97 : {
98 0 : nCacheMax = atoi(CPLGetConfigOption("GDAL_CACHEMAX","10"));
99 0 : if( nCacheMax < 10000 )
100 0 : nCacheMax *= 1024 * 1024;
101 : }
102 287 : bCacheMaxInitialized = TRUE;
103 : }
104 :
105 270277 : return nCacheMax;
106 : }
107 :
108 : /************************************************************************/
109 : /* GDALGetCacheUsed() */
110 : /************************************************************************/
111 :
112 : /**
113 : * \brief Get cache memory used.
114 : *
115 : * @return the number of bytes of memory currently in use by the
116 : * GDALRasterBlock memory caching.
117 : */
118 :
119 2 : int CPL_STDCALL GDALGetCacheUsed()
120 : {
121 2 : return nCacheUsed;
122 : }
123 :
124 : /************************************************************************/
125 : /* GDALFlushCacheBlock() */
126 : /* */
127 : /* The workhorse of cache management! */
128 : /************************************************************************/
129 :
130 : /**
131 : * \brief Try to flush one cached raster block
132 : *
133 : * This function will search the first unlocked raster block and will
134 : * flush it to release the associated memory.
135 : *
136 : * @return TRUE if one block was flushed, FALSE if there are no cached blocks
137 : * or if they are currently locked.
138 : */
139 859 : int CPL_STDCALL GDALFlushCacheBlock()
140 :
141 : {
142 859 : return GDALRasterBlock::FlushCacheBlock();
143 : }
144 :
145 : /************************************************************************/
146 : /* ==================================================================== */
147 : /* GDALRasterBlock */
148 : /* ==================================================================== */
149 : /************************************************************************/
150 :
151 : /**
152 : * \class GDALRasterBlock "gdal_priv.h"
153 : *
154 : * GDALRasterBlock objects hold one block of raster data for one band
155 : * that is currently stored in the GDAL raster cache. The cache holds
156 : * some blocks of raster data for zero or more GDALRasterBand objects
157 : * across zero or more GDALDataset objects in a global raster cache with
158 : * a least recently used (LRU) list and an upper cache limit (see
159 : * GDALSetCacheMax()) under which the cache size is normally kept.
160 : *
161 : * Some blocks in the cache may be modified relative to the state on disk
162 : * (they are marked "Dirty") and must be flushed to disk before they can
163 : * be discarded. Other (Clean) blocks may just be discarded if their memory
164 : * needs to be recovered.
165 : *
166 : * In normal situations applications do not interact directly with the
167 : * GDALRasterBlock - instead it it utilized by the RasterIO() interfaces
168 : * to implement caching.
169 : *
170 : * Some driver classes are implemented in a fashion that completely avoids
171 : * use of the GDAL raster cache (and GDALRasterBlock) though this is not very
172 : * common.
173 : */
174 :
175 : /************************************************************************/
176 : /* FlushCacheBlock() */
177 : /* */
178 : /* Note, if we have alot of blocks locked for a long time, this */
179 : /* method is going to get slow because it will have to traverse */
180 : /* the linked list a long ways looking for a flushing */
181 : /* candidate. It might help to re-touch locked blocks to push */
182 : /* them to the top of the list. */
183 : /************************************************************************/
184 :
185 : /**
186 : * \brief Attempt to flush at least one block from the cache.
187 : *
188 : * This static method is normally used to recover memory when a request
189 : * for a new cache block would put cache memory use over the established
190 : * limit.
191 : *
192 : * C++ analog to the C function GDALFlushCacheBlock().
193 : *
194 : * @return TRUE if successful or FALSE if no flushable block is found.
195 : */
196 :
197 859 : int GDALRasterBlock::FlushCacheBlock()
198 :
199 : {
200 : int nXOff, nYOff;
201 : GDALRasterBand *poBand;
202 :
203 : {
204 859 : CPLMutexHolderD( &hRBMutex );
205 859 : GDALRasterBlock *poTarget = (GDALRasterBlock *) poOldest;
206 :
207 1718 : while( poTarget != NULL && poTarget->GetLockCount() > 0 )
208 0 : poTarget = poTarget->poPrevious;
209 :
210 859 : if( poTarget == NULL )
211 431 : return FALSE;
212 :
213 428 : poTarget->Detach();
214 :
215 428 : nXOff = poTarget->GetXOff();
216 428 : nYOff = poTarget->GetYOff();
217 428 : poBand = poTarget->GetBand();
218 : }
219 :
220 428 : poBand->FlushBlock( nXOff, nYOff );
221 :
222 428 : return TRUE;
223 : }
224 :
225 : /************************************************************************/
226 : /* GDALRasterBlock() */
227 : /************************************************************************/
228 :
229 : /**
230 : * @brief GDALRasterBlock Constructor
231 : *
232 : * Normally only called from GDALRasterBand::GetLockedBlockRef().
233 : *
234 : * @param poBandIn the raster band used as source of raster block
235 : * being constructed.
236 : *
237 : * @param nXOffIn the horizontal block offset, with zero indicating
238 : * the left most block, 1 the next block and so forth.
239 : *
240 : * @param nYOffIn the vertical block offset, with zero indicating
241 : * the top most block, 1 the next block and so forth.
242 : */
243 :
244 269459 : GDALRasterBlock::GDALRasterBlock( GDALRasterBand *poBandIn,
245 269459 : int nXOffIn, int nYOffIn )
246 :
247 : {
248 : CPLAssert( NULL != poBandIn );
249 :
250 269459 : poBand = poBandIn;
251 :
252 269459 : poBand->GetBlockSize( &nXSize, &nYSize );
253 269459 : eType = poBand->GetRasterDataType();
254 269459 : pData = NULL;
255 269459 : bDirty = FALSE;
256 269459 : nLockCount = 0;
257 :
258 269459 : poNext = poPrevious = NULL;
259 :
260 269459 : nXOff = nXOffIn;
261 269459 : nYOff = nYOffIn;
262 269459 : }
263 :
264 : /************************************************************************/
265 : /* ~GDALRasterBlock() */
266 : /************************************************************************/
267 :
268 : /**
269 : * Block destructor.
270 : *
271 : * Normally called from GDALRasterBand::FlushBlock().
272 : */
273 :
274 538918 : GDALRasterBlock::~GDALRasterBlock()
275 :
276 : {
277 269459 : Detach();
278 :
279 269459 : if( pData != NULL )
280 : {
281 : int nSizeInBytes;
282 :
283 269459 : VSIFree( pData );
284 :
285 269459 : nSizeInBytes = (nXSize * nYSize * GDALGetDataTypeSize(eType)+7)/8;
286 :
287 : {
288 269459 : CPLMutexHolderD( &hRBMutex );
289 269459 : nCacheUsed -= nSizeInBytes;
290 : }
291 : }
292 :
293 : CPLAssert( nLockCount == 0 );
294 :
295 : #ifdef ENABLE_DEBUG
296 : Verify();
297 : #endif
298 538918 : }
299 :
300 : /************************************************************************/
301 : /* Detach() */
302 : /************************************************************************/
303 :
304 : /**
305 : * Remove block from cache.
306 : *
307 : * This method removes the current block from the linked list used to keep
308 : * track of all cached blocks in order of age. It does not affect whether
309 : * the block is referenced by a GDALRasterBand nor does it destroy or flush
310 : * the block.
311 : */
312 :
313 539346 : void GDALRasterBlock::Detach()
314 :
315 : {
316 539346 : CPLMutexHolderD( &hRBMutex );
317 :
318 539346 : if( poOldest == this )
319 2824 : poOldest = poPrevious;
320 :
321 539346 : if( poNewest == this )
322 : {
323 269887 : poNewest = poNext;
324 : }
325 :
326 539346 : if( poPrevious != NULL )
327 0 : poPrevious->poNext = poNext;
328 :
329 539346 : if( poNext != NULL )
330 267063 : poNext->poPrevious = poPrevious;
331 :
332 539346 : poPrevious = NULL;
333 539346 : poNext = NULL;
334 539346 : }
335 :
336 : /************************************************************************/
337 : /* Verify() */
338 : /************************************************************************/
339 :
340 : /**
341 : * Confirms (via assertions) that the block cache linked list is in a
342 : * consistent state.
343 : */
344 :
345 0 : void GDALRasterBlock::Verify()
346 :
347 : {
348 0 : CPLMutexHolderD( &hRBMutex );
349 :
350 : CPLAssert( (poNewest == NULL && poOldest == NULL)
351 : || (poNewest != NULL && poOldest != NULL) );
352 :
353 0 : if( poNewest != NULL )
354 : {
355 : CPLAssert( poNewest->poPrevious == NULL );
356 : CPLAssert( poOldest->poNext == NULL );
357 :
358 0 : for( GDALRasterBlock *poBlock = (GDALRasterBlock *) poNewest;
359 : poBlock != NULL;
360 : poBlock = poBlock->poNext )
361 : {
362 0 : if( poBlock->poPrevious )
363 : {
364 : CPLAssert( poBlock->poPrevious->poNext == poBlock );
365 : }
366 :
367 0 : if( poBlock->poNext )
368 : {
369 : CPLAssert( poBlock->poNext->poPrevious == poBlock );
370 : }
371 : }
372 0 : }
373 0 : }
374 :
375 : /************************************************************************/
376 : /* Write() */
377 : /************************************************************************/
378 :
379 : /**
380 : * Force writing of the current block, if dirty.
381 : *
382 : * The block is written using GDALRasterBand::IWriteBlock() on it's
383 : * corresponding band object. Even if the write fails the block will
384 : * be marked clean.
385 : *
386 : * @return CE_None otherwise the error returned by IWriteBlock().
387 : */
388 :
389 20301 : CPLErr GDALRasterBlock::Write()
390 :
391 : {
392 20301 : if( !GetDirty() )
393 0 : return CE_None;
394 :
395 20301 : if( poBand == NULL )
396 0 : return CE_Failure;
397 :
398 20301 : MarkClean();
399 :
400 20301 : return poBand->IWriteBlock( nXOff, nYOff, pData );
401 : }
402 :
403 : /************************************************************************/
404 : /* Touch() */
405 : /************************************************************************/
406 :
407 : /**
408 : * Push block to top of LRU (least-recently used) list.
409 : *
410 : * This method is normally called when a block is used to keep track
411 : * that it has been recently used.
412 : */
413 :
414 4329148 : void GDALRasterBlock::Touch()
415 :
416 : {
417 4329148 : CPLMutexHolderD( &hRBMutex );
418 :
419 4329148 : if( poNewest == this )
420 : return;
421 :
422 3674225 : if( poOldest == this )
423 1193611 : poOldest = this->poPrevious;
424 :
425 3674225 : if( poPrevious != NULL )
426 3404338 : poPrevious->poNext = poNext;
427 :
428 3674225 : if( poNext != NULL )
429 2210727 : poNext->poPrevious = poPrevious;
430 :
431 3674225 : poPrevious = NULL;
432 3674225 : poNext = (GDALRasterBlock *) poNewest;
433 :
434 3674225 : if( poNewest != NULL )
435 : {
436 : CPLAssert( poNewest->poPrevious == NULL );
437 3671401 : poNewest->poPrevious = this;
438 : }
439 3674225 : poNewest = this;
440 :
441 3674225 : if( poOldest == NULL )
442 : {
443 : CPLAssert( poPrevious == NULL && poNext == NULL );
444 2824 : poOldest = this;
445 4329148 : }
446 : #ifdef ENABLE_DEBUG
447 : Verify();
448 : #endif
449 : }
450 :
451 : /************************************************************************/
452 : /* Internalize() */
453 : /************************************************************************/
454 :
455 : /**
456 : * Allocate memory for block.
457 : *
458 : * This method allocates memory for the block, and attempts to flush other
459 : * blocks, if necessary, to bring the total cache size back within the limits.
460 : * The newly allocated block is touched and will be considered most recently
461 : * used in the LRU list.
462 : *
463 : * @return CE_None on success or CE_Failure if memory allocation fails.
464 : */
465 :
466 269459 : CPLErr GDALRasterBlock::Internalize()
467 :
468 : {
469 269459 : CPLMutexHolderD( &hRBMutex );
470 : void *pNewData;
471 : int nSizeInBytes;
472 269459 : int nCurCacheMax = GDALGetCacheMax();
473 :
474 : /* No risk of overflow as it is checked in GDALRasterBand::InitBlockInfo() */
475 269459 : nSizeInBytes = nXSize * nYSize * (GDALGetDataTypeSize(eType) / 8);
476 :
477 269459 : pNewData = VSIMalloc( nSizeInBytes );
478 269459 : if( pNewData == NULL )
479 : {
480 : CPLError( CE_Failure, CPLE_OutOfMemory,
481 : "GDALRasterBlock::Internalize : Out of memory allocating %d bytes.",
482 0 : nSizeInBytes);
483 0 : return( CE_Failure );
484 : }
485 :
486 269459 : if( pData != NULL )
487 0 : memcpy( pNewData, pData, nSizeInBytes );
488 :
489 269459 : pData = pNewData;
490 :
491 : /* -------------------------------------------------------------------- */
492 : /* Flush old blocks if we are nearing our memory limit. */
493 : /* -------------------------------------------------------------------- */
494 269459 : AddLock(); /* don't flush this block! */
495 :
496 269459 : nCacheUsed += nSizeInBytes;
497 539346 : while( nCacheUsed > nCurCacheMax )
498 : {
499 859 : int nOldCacheUsed = nCacheUsed;
500 :
501 859 : GDALFlushCacheBlock();
502 :
503 859 : if( nCacheUsed == nOldCacheUsed )
504 431 : break;
505 : }
506 :
507 : /* -------------------------------------------------------------------- */
508 : /* Add this block to the list. */
509 : /* -------------------------------------------------------------------- */
510 269459 : Touch();
511 269459 : DropLock();
512 :
513 269459 : return( CE_None );
514 : }
515 :
516 : /************************************************************************/
517 : /* MarkDirty() */
518 : /************************************************************************/
519 :
520 : /**
521 : * Mark the block as modified.
522 : *
523 : * A dirty block is one that has been modified and will need to be written
524 : * to disk before it can be flushed.
525 : */
526 :
527 1397274 : void GDALRasterBlock::MarkDirty()
528 :
529 : {
530 1397274 : bDirty = TRUE;
531 1397274 : }
532 :
533 :
534 : /************************************************************************/
535 : /* MarkClean() */
536 : /************************************************************************/
537 :
538 : /**
539 : * Mark the block as unmodified.
540 : *
541 : * A dirty block is one that has been modified and will need to be written
542 : * to disk before it can be flushed.
543 : */
544 :
545 21552 : void GDALRasterBlock::MarkClean()
546 :
547 : {
548 21552 : bDirty = FALSE;
549 21552 : }
550 :
551 : /************************************************************************/
552 : /* SafeLockBlock() */
553 : /************************************************************************/
554 :
555 : /**
556 : * \brief Safely lock block.
557 : *
558 : * This method locks a GDALRasterBlock (and touches it) in a thread-safe
559 : * manner. The global block cache mutex is held while locking the block,
560 : * in order to avoid race conditions with other threads that might be
561 : * trying to expire the block at the same time. The block pointer may be
562 : * safely NULL, in which case this method does nothing.
563 : *
564 : * @param ppBlock Pointer to the block pointer to try and lock/touch.
565 : */
566 :
567 4059767 : int GDALRasterBlock::SafeLockBlock( GDALRasterBlock ** ppBlock )
568 :
569 : {
570 : CPLAssert( NULL != ppBlock );
571 :
572 4059767 : CPLMutexHolderD( &hRBMutex );
573 :
574 4059767 : if( *ppBlock != NULL )
575 : {
576 3790230 : (*ppBlock)->AddLock();
577 3790230 : (*ppBlock)->Touch();
578 :
579 3790230 : return TRUE;
580 : }
581 : else
582 269537 : return FALSE;
583 : }
|