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>