| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 ** 2008 November 18 | |
| 3 ** | |
| 4 ** The author disclaims copyright to this source code. In place of | |
| 5 ** a legal notice, here is a blessing: | |
| 6 ** | |
| 7 ** May you do good and not evil. | |
| 8 ** May you find forgiveness for yourself and forgive others. | |
| 9 ** May you share freely, never taking more than you give. | |
| 10 ** | |
| 11 ************************************************************************* | |
| 12 ** | |
| 13 ** This file contains code used for testing the SQLite system. | |
| 14 ** None of the code in this file goes into a deliverable build. | |
| 15 ** | |
| 16 ** This file contains an application-defined pager cache | |
| 17 ** implementation that can be plugged in in place of the | |
| 18 ** default pcache. This alternative pager cache will throw | |
| 19 ** some errors that the default cache does not. | |
| 20 ** | |
| 21 ** This pagecache implementation is designed for simplicity | |
| 22 ** not speed. | |
| 23 ** | |
| 24 ** $Id: test_pcache.c,v 1.3 2009/04/11 11:38:54 drh Exp $ | |
| 25 */ | |
| 26 #include "sqlite3.h" | |
| 27 #include <string.h> | |
| 28 #include <assert.h> | |
| 29 | |
| 30 /* | |
| 31 ** Global data used by this test implementation. There is no | |
| 32 ** mutexing, which means this page cache will not work in a | |
| 33 ** multi-threaded test. | |
| 34 */ | |
| 35 typedef struct testpcacheGlobalType testpcacheGlobalType; | |
| 36 struct testpcacheGlobalType { | |
| 37 void *pDummy; /* Dummy allocation to simulate failures */ | |
| 38 int nInstance; /* Number of current instances */ | |
| 39 unsigned discardChance; /* Chance of discarding on an unpin (0-100) */ | |
| 40 unsigned prngSeed; /* Seed for the PRNG */ | |
| 41 unsigned highStress; /* Call xStress agressively */ | |
| 42 }; | |
| 43 static testpcacheGlobalType testpcacheGlobal; | |
| 44 | |
| 45 /* | |
| 46 ** Initializer. | |
| 47 ** | |
| 48 ** Verify that the initializer is only called when the system is | |
| 49 ** uninitialized. Allocate some memory and report SQLITE_NOMEM if | |
| 50 ** the allocation fails. This provides a means to test the recovery | |
| 51 ** from a failed initialization attempt. It also verifies that the | |
| 52 ** the destructor always gets call - otherwise there would be a | |
| 53 ** memory leak. | |
| 54 */ | |
| 55 static int testpcacheInit(void *pArg){ | |
| 56 assert( pArg==(void*)&testpcacheGlobal ); | |
| 57 assert( testpcacheGlobal.pDummy==0 ); | |
| 58 assert( testpcacheGlobal.nInstance==0 ); | |
| 59 testpcacheGlobal.pDummy = sqlite3_malloc(10); | |
| 60 return testpcacheGlobal.pDummy==0 ? SQLITE_NOMEM : SQLITE_OK; | |
| 61 } | |
| 62 | |
| 63 /* | |
| 64 ** Destructor | |
| 65 ** | |
| 66 ** Verify that this is only called after initialization. | |
| 67 ** Free the memory allocated by the initializer. | |
| 68 */ | |
| 69 static void testpcacheShutdown(void *pArg){ | |
| 70 assert( pArg==(void*)&testpcacheGlobal ); | |
| 71 assert( testpcacheGlobal.pDummy!=0 ); | |
| 72 assert( testpcacheGlobal.nInstance==0 ); | |
| 73 sqlite3_free( testpcacheGlobal.pDummy ); | |
| 74 testpcacheGlobal.pDummy = 0; | |
| 75 } | |
| 76 | |
| 77 /* | |
| 78 ** Number of pages in a cache. | |
| 79 ** | |
| 80 ** The number of pages is a hard upper bound in this test module. | |
| 81 ** If more pages are requested, sqlite3PcacheFetch() returns NULL. | |
| 82 ** | |
| 83 ** If testing with in-memory temp tables, provide a larger pcache. | |
| 84 ** Some of the test cases need this. | |
| 85 */ | |
| 86 #if defined(SQLITE_TEMP_STORE) && SQLITE_TEMP_STORE>=2 | |
| 87 # define TESTPCACHE_NPAGE 499 | |
| 88 #else | |
| 89 # define TESTPCACHE_NPAGE 217 | |
| 90 #endif | |
| 91 #define TESTPCACHE_RESERVE 17 | |
| 92 | |
| 93 /* | |
| 94 ** Magic numbers used to determine validity of the page cache. | |
| 95 */ | |
| 96 #define TESTPCACHE_VALID 0x364585fd | |
| 97 #define TESTPCACHE_CLEAR 0xd42670d4 | |
| 98 | |
| 99 /* | |
| 100 ** Private implementation of a page cache. | |
| 101 */ | |
| 102 typedef struct testpcache testpcache; | |
| 103 struct testpcache { | |
| 104 int szPage; /* Size of each page. Multiple of 8. */ | |
| 105 int bPurgeable; /* True if the page cache is purgeable */ | |
| 106 int nFree; /* Number of unused slots in a[] */ | |
| 107 int nPinned; /* Number of pinned slots in a[] */ | |
| 108 unsigned iRand; /* State of the PRNG */ | |
| 109 unsigned iMagic; /* Magic number for sanity checking */ | |
| 110 struct testpcachePage { | |
| 111 unsigned key; /* The key for this page. 0 means unallocated */ | |
| 112 int isPinned; /* True if the page is pinned */ | |
| 113 void *pData; /* Data for this page */ | |
| 114 } a[TESTPCACHE_NPAGE]; /* All pages in the cache */ | |
| 115 }; | |
| 116 | |
| 117 /* | |
| 118 ** Get a random number using the PRNG in the given page cache. | |
| 119 */ | |
| 120 static unsigned testpcacheRandom(testpcache *p){ | |
| 121 unsigned x = 0; | |
| 122 int i; | |
| 123 for(i=0; i<4; i++){ | |
| 124 p->iRand = (p->iRand*69069 + 5); | |
| 125 x = (x<<8) | ((p->iRand>>16)&0xff); | |
| 126 } | |
| 127 return x; | |
| 128 } | |
| 129 | |
| 130 | |
| 131 /* | |
| 132 ** Allocate a new page cache instance. | |
| 133 */ | |
| 134 static sqlite3_pcache *testpcacheCreate(int szPage, int bPurgeable){ | |
| 135 int nMem; | |
| 136 char *x; | |
| 137 testpcache *p; | |
| 138 int i; | |
| 139 assert( testpcacheGlobal.pDummy!=0 ); | |
| 140 szPage = (szPage+7)&~7; | |
| 141 nMem = sizeof(testpcache) + TESTPCACHE_NPAGE*szPage; | |
| 142 p = sqlite3_malloc( nMem ); | |
| 143 if( p==0 ) return 0; | |
| 144 x = (char*)&p[1]; | |
| 145 p->szPage = szPage; | |
| 146 p->nFree = TESTPCACHE_NPAGE; | |
| 147 p->nPinned = 0; | |
| 148 p->iRand = testpcacheGlobal.prngSeed; | |
| 149 p->bPurgeable = bPurgeable; | |
| 150 p->iMagic = TESTPCACHE_VALID; | |
| 151 for(i=0; i<TESTPCACHE_NPAGE; i++, x += szPage){ | |
| 152 p->a[i].key = 0; | |
| 153 p->a[i].isPinned = 0; | |
| 154 p->a[i].pData = (void*)x; | |
| 155 } | |
| 156 testpcacheGlobal.nInstance++; | |
| 157 return (sqlite3_pcache*)p; | |
| 158 } | |
| 159 | |
| 160 /* | |
| 161 ** Set the cache size | |
| 162 */ | |
| 163 static void testpcacheCachesize(sqlite3_pcache *pCache, int newSize){ | |
| 164 testpcache *p = (testpcache*)pCache; | |
| 165 assert( p->iMagic==TESTPCACHE_VALID ); | |
| 166 assert( newSize>=1 ); | |
| 167 assert( testpcacheGlobal.pDummy!=0 ); | |
| 168 assert( testpcacheGlobal.nInstance>0 ); | |
| 169 } | |
| 170 | |
| 171 /* | |
| 172 ** Return the number of pages in the cache that are being used. | |
| 173 ** This includes both pinned and unpinned pages. | |
| 174 */ | |
| 175 static int testpcachePagecount(sqlite3_pcache *pCache){ | |
| 176 testpcache *p = (testpcache*)pCache; | |
| 177 assert( p->iMagic==TESTPCACHE_VALID ); | |
| 178 assert( testpcacheGlobal.pDummy!=0 ); | |
| 179 assert( testpcacheGlobal.nInstance>0 ); | |
| 180 return TESTPCACHE_NPAGE - p->nFree; | |
| 181 } | |
| 182 | |
| 183 /* | |
| 184 ** Fetch a page. | |
| 185 */ | |
| 186 static void *testpcacheFetch( | |
| 187 sqlite3_pcache *pCache, | |
| 188 unsigned key, | |
| 189 int createFlag | |
| 190 ){ | |
| 191 testpcache *p = (testpcache*)pCache; | |
| 192 int i, j; | |
| 193 assert( p->iMagic==TESTPCACHE_VALID ); | |
| 194 assert( testpcacheGlobal.pDummy!=0 ); | |
| 195 assert( testpcacheGlobal.nInstance>0 ); | |
| 196 | |
| 197 /* See if the page is already in cache. Return immediately if it is */ | |
| 198 for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| 199 if( p->a[i].key==key ){ | |
| 200 if( !p->a[i].isPinned ){ | |
| 201 p->nPinned++; | |
| 202 assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); | |
| 203 p->a[i].isPinned = 1; | |
| 204 } | |
| 205 return p->a[i].pData; | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 /* If createFlag is 0, never allocate a new page */ | |
| 210 if( createFlag==0 ){ | |
| 211 return 0; | |
| 212 } | |
| 213 | |
| 214 /* If no pages are available, always fail */ | |
| 215 if( p->nPinned==TESTPCACHE_NPAGE ){ | |
| 216 return 0; | |
| 217 } | |
| 218 | |
| 219 /* Do not allocate the last TESTPCACHE_RESERVE pages unless createFlag is 2 */ | |
| 220 if( p->nPinned>=TESTPCACHE_NPAGE-TESTPCACHE_RESERVE && createFlag<2 ){ | |
| 221 return 0; | |
| 222 } | |
| 223 | |
| 224 /* Do not allocate if highStress is enabled and createFlag is not 2. | |
| 225 ** | |
| 226 ** The highStress setting causes pagerStress() to be called much more | |
| 227 ** often, which exercises the pager logic more intensely. | |
| 228 */ | |
| 229 if( testpcacheGlobal.highStress && createFlag<2 ){ | |
| 230 return 0; | |
| 231 } | |
| 232 | |
| 233 /* Find a free page to allocate if there are any free pages. | |
| 234 ** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2. | |
| 235 */ | |
| 236 if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){ | |
| 237 j = testpcacheRandom(p) % TESTPCACHE_NPAGE; | |
| 238 for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ | |
| 239 if( p->a[j].key==0 ){ | |
| 240 p->a[j].key = key; | |
| 241 p->a[j].isPinned = 1; | |
| 242 memset(p->a[j].pData, 0, p->szPage); | |
| 243 p->nPinned++; | |
| 244 p->nFree--; | |
| 245 assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); | |
| 246 return p->a[j].pData; | |
| 247 } | |
| 248 } | |
| 249 | |
| 250 /* The prior loop always finds a freepage to allocate */ | |
| 251 assert( 0 ); | |
| 252 } | |
| 253 | |
| 254 /* If this cache is not purgeable then we have to fail. | |
| 255 */ | |
| 256 if( p->bPurgeable==0 ){ | |
| 257 return 0; | |
| 258 } | |
| 259 | |
| 260 /* If there are no free pages, recycle a page. The page to | |
| 261 ** recycle is selected at random from all unpinned pages. | |
| 262 */ | |
| 263 j = testpcacheRandom(p) % TESTPCACHE_NPAGE; | |
| 264 for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ | |
| 265 if( p->a[j].key>0 && p->a[j].isPinned==0 ){ | |
| 266 p->a[j].key = key; | |
| 267 p->a[j].isPinned = 1; | |
| 268 memset(p->a[j].pData, 0, p->szPage); | |
| 269 p->nPinned++; | |
| 270 assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); | |
| 271 return p->a[j].pData; | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 /* The previous loop always finds a page to recycle. */ | |
| 276 assert(0); | |
| 277 return 0; | |
| 278 } | |
| 279 | |
| 280 /* | |
| 281 ** Unpin a page. | |
| 282 */ | |
| 283 static void testpcacheUnpin( | |
| 284 sqlite3_pcache *pCache, | |
| 285 void *pOldPage, | |
| 286 int discard | |
| 287 ){ | |
| 288 testpcache *p = (testpcache*)pCache; | |
| 289 int i; | |
| 290 assert( p->iMagic==TESTPCACHE_VALID ); | |
| 291 assert( testpcacheGlobal.pDummy!=0 ); | |
| 292 assert( testpcacheGlobal.nInstance>0 ); | |
| 293 | |
| 294 /* Randomly discard pages as they are unpinned according to the | |
| 295 ** discardChance setting. If discardChance is 0, the random discard | |
| 296 ** never happens. If discardChance is 100, it always happens. | |
| 297 */ | |
| 298 if( p->bPurgeable | |
| 299 && (100-testpcacheGlobal.discardChance) <= (testpcacheRandom(p)%100) | |
| 300 ){ | |
| 301 discard = 1; | |
| 302 } | |
| 303 | |
| 304 for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| 305 if( p->a[i].pData==pOldPage ){ | |
| 306 /* The pOldPage pointer always points to a pinned page */ | |
| 307 assert( p->a[i].isPinned ); | |
| 308 p->a[i].isPinned = 0; | |
| 309 p->nPinned--; | |
| 310 assert( p->nPinned>=0 ); | |
| 311 if( discard ){ | |
| 312 p->a[i].key = 0; | |
| 313 p->nFree++; | |
| 314 assert( p->nFree<=TESTPCACHE_NPAGE ); | |
| 315 } | |
| 316 return; | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 /* The pOldPage pointer always points to a valid page */ | |
| 321 assert( 0 ); | |
| 322 } | |
| 323 | |
| 324 | |
| 325 /* | |
| 326 ** Rekey a single page. | |
| 327 */ | |
| 328 static void testpcacheRekey( | |
| 329 sqlite3_pcache *pCache, | |
| 330 void *pOldPage, | |
| 331 unsigned oldKey, | |
| 332 unsigned newKey | |
| 333 ){ | |
| 334 testpcache *p = (testpcache*)pCache; | |
| 335 int i; | |
| 336 assert( p->iMagic==TESTPCACHE_VALID ); | |
| 337 assert( testpcacheGlobal.pDummy!=0 ); | |
| 338 assert( testpcacheGlobal.nInstance>0 ); | |
| 339 | |
| 340 /* If there already exists another page at newKey, verify that | |
| 341 ** the other page is unpinned and discard it. | |
| 342 */ | |
| 343 for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| 344 if( p->a[i].key==newKey ){ | |
| 345 /* The new key is never a page that is already pinned */ | |
| 346 assert( p->a[i].isPinned==0 ); | |
| 347 p->a[i].key = 0; | |
| 348 p->nFree++; | |
| 349 assert( p->nFree<=TESTPCACHE_NPAGE ); | |
| 350 break; | |
| 351 } | |
| 352 } | |
| 353 | |
| 354 /* Find the page to be rekeyed and rekey it. | |
| 355 */ | |
| 356 for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| 357 if( p->a[i].key==oldKey ){ | |
| 358 /* The oldKey and pOldPage parameters match */ | |
| 359 assert( p->a[i].pData==pOldPage ); | |
| 360 /* Page to be rekeyed must be pinned */ | |
| 361 assert( p->a[i].isPinned ); | |
| 362 p->a[i].key = newKey; | |
| 363 return; | |
| 364 } | |
| 365 } | |
| 366 | |
| 367 /* Rekey is always given a valid page to work with */ | |
| 368 assert( 0 ); | |
| 369 } | |
| 370 | |
| 371 | |
| 372 /* | |
| 373 ** Truncate the page cache. Every page with a key of iLimit or larger | |
| 374 ** is discarded. | |
| 375 */ | |
| 376 static void testpcacheTruncate(sqlite3_pcache *pCache, unsigned iLimit){ | |
| 377 testpcache *p = (testpcache*)pCache; | |
| 378 unsigned int i; | |
| 379 assert( p->iMagic==TESTPCACHE_VALID ); | |
| 380 assert( testpcacheGlobal.pDummy!=0 ); | |
| 381 assert( testpcacheGlobal.nInstance>0 ); | |
| 382 for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| 383 if( p->a[i].key>=iLimit ){ | |
| 384 p->a[i].key = 0; | |
| 385 if( p->a[i].isPinned ){ | |
| 386 p->nPinned--; | |
| 387 assert( p->nPinned>=0 ); | |
| 388 } | |
| 389 p->nFree++; | |
| 390 assert( p->nFree<=TESTPCACHE_NPAGE ); | |
| 391 } | |
| 392 } | |
| 393 } | |
| 394 | |
| 395 /* | |
| 396 ** Destroy a page cache. | |
| 397 */ | |
| 398 static void testpcacheDestroy(sqlite3_pcache *pCache){ | |
| 399 testpcache *p = (testpcache*)pCache; | |
| 400 assert( p->iMagic==TESTPCACHE_VALID ); | |
| 401 assert( testpcacheGlobal.pDummy!=0 ); | |
| 402 assert( testpcacheGlobal.nInstance>0 ); | |
| 403 p->iMagic = TESTPCACHE_CLEAR; | |
| 404 sqlite3_free(p); | |
| 405 testpcacheGlobal.nInstance--; | |
| 406 } | |
| 407 | |
| 408 | |
| 409 /* | |
| 410 ** Invoke this routine to register or unregister the testing pager cache | |
| 411 ** implemented by this file. | |
| 412 ** | |
| 413 ** Install the test pager cache if installFlag is 1 and uninstall it if | |
| 414 ** installFlag is 0. | |
| 415 ** | |
| 416 ** When installing, discardChance is a number between 0 and 100 that | |
| 417 ** indicates the probability of discarding a page when unpinning the | |
| 418 ** page. 0 means never discard (unless the discard flag is set). | |
| 419 ** 100 means always discard. | |
| 420 */ | |
| 421 void installTestPCache( | |
| 422 int installFlag, /* True to install. False to uninstall. */ | |
| 423 unsigned discardChance, /* 0-100. Chance to discard on unpin */ | |
| 424 unsigned prngSeed, /* Seed for the PRNG */ | |
| 425 unsigned highStress /* Call xStress agressively */ | |
| 426 ){ | |
| 427 static const sqlite3_pcache_methods testPcache = { | |
| 428 (void*)&testpcacheGlobal, | |
| 429 testpcacheInit, | |
| 430 testpcacheShutdown, | |
| 431 testpcacheCreate, | |
| 432 testpcacheCachesize, | |
| 433 testpcachePagecount, | |
| 434 testpcacheFetch, | |
| 435 testpcacheUnpin, | |
| 436 testpcacheRekey, | |
| 437 testpcacheTruncate, | |
| 438 testpcacheDestroy, | |
| 439 }; | |
| 440 static sqlite3_pcache_methods defaultPcache; | |
| 441 static int isInstalled = 0; | |
| 442 | |
| 443 assert( testpcacheGlobal.nInstance==0 ); | |
| 444 assert( testpcacheGlobal.pDummy==0 ); | |
| 445 assert( discardChance<=100 ); | |
| 446 testpcacheGlobal.discardChance = discardChance; | |
| 447 testpcacheGlobal.prngSeed = prngSeed ^ (prngSeed<<16); | |
| 448 testpcacheGlobal.highStress = highStress; | |
| 449 if( installFlag!=isInstalled ){ | |
| 450 if( installFlag ){ | |
| 451 sqlite3_config(SQLITE_CONFIG_GETPCACHE, &defaultPcache); | |
| 452 assert( defaultPcache.xCreate!=testpcacheCreate ); | |
| 453 sqlite3_config(SQLITE_CONFIG_PCACHE, &testPcache); | |
| 454 }else{ | |
| 455 assert( defaultPcache.xCreate!=0 ); | |
| 456 sqlite3_config(SQLITE_CONFIG_PCACHE, &defaultPcache); | |
| 457 } | |
| 458 isInstalled = installFlag; | |
| 459 } | |
| 460 } | |
| OLD | NEW |