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