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.2 2009/01/07 03:59:47 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 #define TESTPCACHE_NPAGE 217 81 #define TESTPCACHE_RESERVE 17 82 83 /* 84 ** Magic numbers used to determine validity of the page cache. 85 */ 86 #define TESTPCACHE_VALID 0x364585fd 87 #define TESTPCACHE_CLEAR 0xd42670d4 88 89 /* 90 ** Private implementation of a page cache. 91 */ 92 typedef struct testpcache testpcache; 93 struct testpcache { 94 int szPage; /* Size of each page. Multiple of 8. */ 95 int bPurgeable; /* True if the page cache is purgeable */ 96 int nFree; /* Number of unused slots in a[] */ 97 int nPinned; /* Number of pinned slots in a[] */ 98 unsigned iRand; /* State of the PRNG */ 99 unsigned iMagic; /* Magic number for sanity checking */ 100 struct testpcachePage { 101 unsigned key; /* The key for this page. 0 means unallocated */ 102 int isPinned; /* True if the page is pinned */ 103 void *pData; /* Data for this page */ 104 } a[TESTPCACHE_NPAGE]; /* All pages in the cache */ 105 }; 106 107 /* 108 ** Get a random number using the PRNG in the given page cache. 109 */ 110 static unsigned testpcacheRandom(testpcache *p){ 111 unsigned x = 0; 112 int i; 113 for(i=0; i<4; i++){ 114 p->iRand = (p->iRand*69069 + 5); 115 x = (x<<8) | ((p->iRand>>16)&0xff); 116 } 117 return x; 118 } 119 120 121 /* 122 ** Allocate a new page cache instance. 123 */ 124 static sqlite3_pcache *testpcacheCreate(int szPage, int bPurgeable){ 125 int nMem; 126 char *x; 127 testpcache *p; 128 int i; 129 assert( testpcacheGlobal.pDummy!=0 ); 130 szPage = (szPage+7)&~7; 131 nMem = sizeof(testpcache) + TESTPCACHE_NPAGE*szPage; 132 p = sqlite3_malloc( nMem ); 133 if( p==0 ) return 0; 134 x = (char*)&p[1]; 135 p->szPage = szPage; 136 p->nFree = TESTPCACHE_NPAGE; 137 p->nPinned = 0; 138 p->iRand = testpcacheGlobal.prngSeed; 139 p->bPurgeable = bPurgeable; 140 p->iMagic = TESTPCACHE_VALID; 141 for(i=0; i<TESTPCACHE_NPAGE; i++, x += szPage){ 142 p->a[i].key = 0; 143 p->a[i].isPinned = 0; 144 p->a[i].pData = (void*)x; 145 } 146 testpcacheGlobal.nInstance++; 147 return (sqlite3_pcache*)p; 148 } 149 150 /* 151 ** Set the cache size 152 */ 153 static void testpcacheCachesize(sqlite3_pcache *pCache, int newSize){ 154 testpcache *p = (testpcache*)pCache; 155 assert( p->iMagic==TESTPCACHE_VALID ); 156 assert( newSize>=1 ); 157 assert( testpcacheGlobal.pDummy!=0 ); 158 assert( testpcacheGlobal.nInstance>0 ); 159 } 160 161 /* 162 ** Return the number of pages in the cache that are being used. 163 ** This includes both pinned and unpinned pages. 164 */ 165 static int testpcachePagecount(sqlite3_pcache *pCache){ 166 testpcache *p = (testpcache*)pCache; 167 assert( p->iMagic==TESTPCACHE_VALID ); 168 assert( testpcacheGlobal.pDummy!=0 ); 169 assert( testpcacheGlobal.nInstance>0 ); 170 return TESTPCACHE_NPAGE - p->nFree; 171 } 172 173 /* 174 ** Fetch a page. 175 */ 176 static void *testpcacheFetch( 177 sqlite3_pcache *pCache, 178 unsigned key, 179 int createFlag 180 ){ 181 testpcache *p = (testpcache*)pCache; 182 int i, j; 183 assert( p->iMagic==TESTPCACHE_VALID ); 184 assert( testpcacheGlobal.pDummy!=0 ); 185 assert( testpcacheGlobal.nInstance>0 ); 186 187 /* See if the page is already in cache. Return immediately if it is */ 188 for(i=0; i<TESTPCACHE_NPAGE; i++){ 189 if( p->a[i].key==key ){ 190 if( !p->a[i].isPinned ){ 191 p->nPinned++; 192 assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); 193 p->a[i].isPinned = 1; 194 } 195 return p->a[i].pData; 196 } 197 } 198 199 /* If createFlag is 0, never allocate a new page */ 200 if( createFlag==0 ){ 201 return 0; 202 } 203 204 /* If no pages are available, always fail */ 205 if( p->nPinned==TESTPCACHE_NPAGE ){ 206 return 0; 207 } 208 209 /* Do not allocate the last TESTPCACHE_RESERVE pages unless createFlag is 2 */ 210 if( p->nPinned>=TESTPCACHE_NPAGE-TESTPCACHE_RESERVE && createFlag<2 ){ 211 return 0; 212 } 213 214 /* Do not allocate if highStress is enabled and createFlag is not 2. 215 ** 216 ** The highStress setting causes pagerStress() to be called much more 217 ** often, which exercises the pager logic more intensely. 218 */ 219 if( testpcacheGlobal.highStress && createFlag<2 ){ 220 return 0; 221 } 222 223 /* Find a free page to allocate if there are any free pages. 224 ** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2. 225 */ 226 if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){ 227 j = testpcacheRandom(p) % TESTPCACHE_NPAGE; 228 for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ 229 if( p->a[j].key==0 ){ 230 p->a[j].key = key; 231 p->a[j].isPinned = 1; 232 memset(p->a[j].pData, 0, p->szPage); 233 p->nPinned++; 234 p->nFree--; 235 assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); 236 return p->a[j].pData; 237 } 238 } 239 240 /* The prior loop always finds a freepage to allocate */ 241 assert( 0 ); 242 } 243 244 /* If this cache is not purgeable then we have to fail. 245 */ 246 if( p->bPurgeable==0 ){ 247 return 0; 248 } 249 250 /* If there are no free pages, recycle a page. The page to 251 ** recycle is selected at random from all unpinned pages. 252 */ 253 j = testpcacheRandom(p) % TESTPCACHE_NPAGE; 254 for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ 255 if( p->a[j].key>0 && p->a[j].isPinned==0 ){ 256 p->a[j].key = key; 257 p->a[j].isPinned = 1; 258 memset(p->a[j].pData, 0, p->szPage); 259 p->nPinned++; 260 assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); 261 return p->a[j].pData; 262 } 263 } 264 265 /* The previous loop always finds a page to recycle. */ 266 assert(0); 267 return 0; 268 } 269 270 /* 271 ** Unpin a page. 272 */ 273 static void testpcacheUnpin( 274 sqlite3_pcache *pCache, 275 void *pOldPage, 276 int discard 277 ){ 278 testpcache *p = (testpcache*)pCache; 279 int i; 280 assert( p->iMagic==TESTPCACHE_VALID ); 281 assert( testpcacheGlobal.pDummy!=0 ); 282 assert( testpcacheGlobal.nInstance>0 ); 283 284 /* Randomly discard pages as they are unpinned according to the 285 ** discardChance setting. If discardChance is 0, the random discard 286 ** never happens. If discardChance is 100, it always happens. 287 */ 288 if( p->bPurgeable 289 && (100-testpcacheGlobal.discardChance) <= (testpcacheRandom(p)%100) 290 ){ 291 discard = 1; 292 } 293 294 for(i=0; i<TESTPCACHE_NPAGE; i++){ 295 if( p->a[i].pData==pOldPage ){ 296 /* The pOldPage pointer always points to a pinned page */ 297 assert( p->a[i].isPinned ); 298 p->a[i].isPinned = 0; 299 p->nPinned--; 300 assert( p->nPinned>=0 ); 301 if( discard ){ 302 p->a[i].key = 0; 303 p->nFree++; 304 assert( p->nFree<=TESTPCACHE_NPAGE ); 305 } 306 return; 307 } 308 } 309 310 /* The pOldPage pointer always points to a valid page */ 311 assert( 0 ); 312 } 313 314 315 /* 316 ** Rekey a single page. 317 */ 318 static void testpcacheRekey( 319 sqlite3_pcache *pCache, 320 void *pOldPage, 321 unsigned oldKey, 322 unsigned newKey 323 ){ 324 testpcache *p = (testpcache*)pCache; 325 int i; 326 assert( p->iMagic==TESTPCACHE_VALID ); 327 assert( testpcacheGlobal.pDummy!=0 ); 328 assert( testpcacheGlobal.nInstance>0 ); 329 330 /* If there already exists another page at newKey, verify that 331 ** the other page is unpinned and discard it. 332 */ 333 for(i=0; i<TESTPCACHE_NPAGE; i++){ 334 if( p->a[i].key==newKey ){ 335 /* The new key is never a page that is already pinned */ 336 assert( p->a[i].isPinned==0 ); 337 p->a[i].key = 0; 338 p->nFree++; 339 assert( p->nFree<=TESTPCACHE_NPAGE ); 340 break; 341 } 342 } 343 344 /* Find the page to be rekeyed and rekey it. 345 */ 346 for(i=0; i<TESTPCACHE_NPAGE; i++){ 347 if( p->a[i].key==oldKey ){ 348 /* The oldKey and pOldPage parameters match */ 349 assert( p->a[i].pData==pOldPage ); 350 /* Page to be rekeyed must be pinned */ 351 assert( p->a[i].isPinned ); 352 p->a[i].key = newKey; 353 return; 354 } 355 } 356 357 /* Rekey is always given a valid page to work with */ 358 assert( 0 ); 359 } 360 361 362 /* 363 ** Truncate the page cache. Every page with a key of iLimit or larger 364 ** is discarded. 365 */ 366 static void testpcacheTruncate(sqlite3_pcache *pCache, unsigned iLimit){ 367 testpcache *p = (testpcache*)pCache; 368 unsigned int i; 369 assert( p->iMagic==TESTPCACHE_VALID ); 370 assert( testpcacheGlobal.pDummy!=0 ); 371 assert( testpcacheGlobal.nInstance>0 ); 372 for(i=0; i<TESTPCACHE_NPAGE; i++){ 373 if( p->a[i].key>=iLimit ){ 374 p->a[i].key = 0; 375 if( p->a[i].isPinned ){ 376 p->nPinned--; 377 assert( p->nPinned>=0 ); 378 } 379 p->nFree++; 380 assert( p->nFree<=TESTPCACHE_NPAGE ); 381 } 382 } 383 } 384 385 /* 386 ** Destroy a page cache. 387 */ 388 static void testpcacheDestroy(sqlite3_pcache *pCache){ 389 testpcache *p = (testpcache*)pCache; 390 assert( p->iMagic==TESTPCACHE_VALID ); 391 assert( testpcacheGlobal.pDummy!=0 ); 392 assert( testpcacheGlobal.nInstance>0 ); 393 p->iMagic = TESTPCACHE_CLEAR; 394 sqlite3_free(p); 395 testpcacheGlobal.nInstance--; 396 } 397 398 399 /* 400 ** Invoke this routine to register or unregister the testing pager cache 401 ** implemented by this file. 402 ** 403 ** Install the test pager cache if installFlag is 1 and uninstall it if 404 ** installFlag is 0. 405 ** 406 ** When installing, discardChance is a number between 0 and 100 that 407 ** indicates the probability of discarding a page when unpinning the 408 ** page. 0 means never discard (unless the discard flag is set). 409 ** 100 means always discard. 410 */ 411 void installTestPCache( 412 int installFlag, /* True to install. False to uninstall. */ 413 unsigned discardChance, /* 0-100. Chance to discard on unpin */ 414 unsigned prngSeed, /* Seed for the PRNG */ 415 unsigned highStress /* Call xStress agressively */ 416 ){ 417 static const sqlite3_pcache_methods testPcache = { 418 (void*)&testpcacheGlobal, 419 testpcacheInit, 420 testpcacheShutdown, 421 testpcacheCreate, 422 testpcacheCachesize, 423 testpcachePagecount, 424 testpcacheFetch, 425 testpcacheUnpin, 426 testpcacheRekey, 427 testpcacheTruncate, 428 testpcacheDestroy, 429 }; 430 static sqlite3_pcache_methods defaultPcache; 431 static int isInstalled = 0; 432 433 assert( testpcacheGlobal.nInstance==0 ); 434 assert( testpcacheGlobal.pDummy==0 ); 435 assert( discardChance<=100 ); 436 testpcacheGlobal.discardChance = discardChance; 437 testpcacheGlobal.prngSeed = prngSeed ^ (prngSeed<<16); 438 testpcacheGlobal.highStress = highStress; 439 if( installFlag!=isInstalled ){ 440 if( installFlag ){ 441 sqlite3_config(SQLITE_CONFIG_GETPCACHE, &defaultPcache); 442 assert( defaultPcache.xCreate!=testpcacheCreate ); 443 sqlite3_config(SQLITE_CONFIG_PCACHE, &testPcache); 444 }else{ 445 assert( defaultPcache.xCreate!=0 ); 446 sqlite3_config(SQLITE_CONFIG_PCACHE, &defaultPcache); 447 } 448 isInstalled = installFlag; 449 } 450 } 451