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 } 461