File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / sqlite3 / src / test_pcache.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 17:04:17 2012 UTC (12 years, 8 months ago) by misho
Branches: sqlite3, MAIN
CVS tags: v3_7_10, HEAD
sqlite3

    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>