Annotation of embedaddon/sqlite3/src/test_pcache.c, revision 1.1
1.1 ! misho 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: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>