Annotation of embedaddon/sqlite3/src/test_pcache.c, revision 1.1.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>