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