Home | History | Annotate | Download | only in src
      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 bPurgeable;           /* True if the page cache is purgeable */
    104   int nFree;                /* Number of unused slots in a[] */
    105   int nPinned;              /* Number of pinned slots in a[] */
    106   unsigned iRand;           /* State of the PRNG */
    107   unsigned iMagic;          /* Magic number for sanity checking */
    108   struct testpcachePage {
    109     unsigned key;              /* The key for this page. 0 means unallocated */
    110     int isPinned;              /* True if the page is pinned */
    111     void *pData;               /* Data for this page */
    112   } a[TESTPCACHE_NPAGE];    /* All pages in the cache */
    113 };
    114 
    115 /*
    116 ** Get a random number using the PRNG in the given page cache.
    117 */
    118 static unsigned testpcacheRandom(testpcache *p){
    119   unsigned x = 0;
    120   int i;
    121   for(i=0; i<4; i++){
    122     p->iRand = (p->iRand*69069 + 5);
    123     x = (x<<8) | ((p->iRand>>16)&0xff);
    124   }
    125   return x;
    126 }
    127 
    128 
    129 /*
    130 ** Allocate a new page cache instance.
    131 */
    132 static sqlite3_pcache *testpcacheCreate(int szPage, int bPurgeable){
    133   int nMem;
    134   char *x;
    135   testpcache *p;
    136   int i;
    137   assert( testpcacheGlobal.pDummy!=0 );
    138   szPage = (szPage+7)&~7;
    139   nMem = sizeof(testpcache) + TESTPCACHE_NPAGE*szPage;
    140   p = sqlite3_malloc( nMem );
    141   if( p==0 ) return 0;
    142   x = (char*)&p[1];
    143   p->szPage = szPage;
    144   p->nFree = TESTPCACHE_NPAGE;
    145   p->nPinned = 0;
    146   p->iRand = testpcacheGlobal.prngSeed;
    147   p->bPurgeable = bPurgeable;
    148   p->iMagic = TESTPCACHE_VALID;
    149   for(i=0; i<TESTPCACHE_NPAGE; i++, x += szPage){
    150     p->a[i].key = 0;
    151     p->a[i].isPinned = 0;
    152     p->a[i].pData = (void*)x;
    153   }
    154   testpcacheGlobal.nInstance++;
    155   return (sqlite3_pcache*)p;
    156 }
    157 
    158 /*
    159 ** Set the cache size
    160 */
    161 static void testpcacheCachesize(sqlite3_pcache *pCache, int newSize){
    162   testpcache *p = (testpcache*)pCache;
    163   assert( p->iMagic==TESTPCACHE_VALID );
    164   assert( newSize>=1 );
    165   assert( testpcacheGlobal.pDummy!=0 );
    166   assert( testpcacheGlobal.nInstance>0 );
    167 }
    168 
    169 /*
    170 ** Return the number of pages in the cache that are being used.
    171 ** This includes both pinned and unpinned pages.
    172 */
    173 static int testpcachePagecount(sqlite3_pcache *pCache){
    174   testpcache *p = (testpcache*)pCache;
    175   assert( p->iMagic==TESTPCACHE_VALID );
    176   assert( testpcacheGlobal.pDummy!=0 );
    177   assert( testpcacheGlobal.nInstance>0 );
    178   return TESTPCACHE_NPAGE - p->nFree;
    179 }
    180 
    181 /*
    182 ** Fetch a page.
    183 */
    184 static void *testpcacheFetch(
    185   sqlite3_pcache *pCache,
    186   unsigned key,
    187   int createFlag
    188 ){
    189   testpcache *p = (testpcache*)pCache;
    190   int i, j;
    191   assert( p->iMagic==TESTPCACHE_VALID );
    192   assert( testpcacheGlobal.pDummy!=0 );
    193   assert( testpcacheGlobal.nInstance>0 );
    194 
    195   /* See if the page is already in cache.  Return immediately if it is */
    196   for(i=0; i<TESTPCACHE_NPAGE; i++){
    197     if( p->a[i].key==key ){
    198       if( !p->a[i].isPinned ){
    199         p->nPinned++;
    200         assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
    201         p->a[i].isPinned = 1;
    202       }
    203       return p->a[i].pData;
    204     }
    205   }
    206 
    207   /* If createFlag is 0, never allocate a new page */
    208   if( createFlag==0 ){
    209     return 0;
    210   }
    211 
    212   /* If no pages are available, always fail */
    213   if( p->nPinned==TESTPCACHE_NPAGE ){
    214     return 0;
    215   }
    216 
    217   /* Do not allocate the last TESTPCACHE_RESERVE pages unless createFlag is 2 */
    218   if( p->nPinned>=TESTPCACHE_NPAGE-TESTPCACHE_RESERVE && createFlag<2 ){
    219     return 0;
    220   }
    221 
    222   /* Do not allocate if highStress is enabled and createFlag is not 2.
    223   **
    224   ** The highStress setting causes pagerStress() to be called much more
    225   ** often, which exercises the pager logic more intensely.
    226   */
    227   if( testpcacheGlobal.highStress && createFlag<2 ){
    228     return 0;
    229   }
    230 
    231   /* Find a free page to allocate if there are any free pages.
    232   ** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2.
    233   */
    234   if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){
    235     j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
    236     for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
    237       if( p->a[j].key==0 ){
    238         p->a[j].key = key;
    239         p->a[j].isPinned = 1;
    240         memset(p->a[j].pData, 0, p->szPage);
    241         p->nPinned++;
    242         p->nFree--;
    243         assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
    244         return p->a[j].pData;
    245       }
    246     }
    247 
    248     /* The prior loop always finds a freepage to allocate */
    249     assert( 0 );
    250   }
    251 
    252   /* If this cache is not purgeable then we have to fail.
    253   */
    254   if( p->bPurgeable==0 ){
    255     return 0;
    256   }
    257 
    258   /* If there are no free pages, recycle a page.  The page to
    259   ** recycle is selected at random from all unpinned pages.
    260   */
    261   j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
    262   for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
    263     if( p->a[j].key>0 && p->a[j].isPinned==0 ){
    264       p->a[j].key = key;
    265       p->a[j].isPinned = 1;
    266       memset(p->a[j].pData, 0, p->szPage);
    267       p->nPinned++;
    268       assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
    269       return p->a[j].pData;
    270     }
    271   }
    272 
    273   /* The previous loop always finds a page to recycle. */
    274   assert(0);
    275   return 0;
    276 }
    277 
    278 /*
    279 ** Unpin a page.
    280 */
    281 static void testpcacheUnpin(
    282   sqlite3_pcache *pCache,
    283   void *pOldPage,
    284   int discard
    285 ){
    286   testpcache *p = (testpcache*)pCache;
    287   int i;
    288   assert( p->iMagic==TESTPCACHE_VALID );
    289   assert( testpcacheGlobal.pDummy!=0 );
    290   assert( testpcacheGlobal.nInstance>0 );
    291 
    292   /* Randomly discard pages as they are unpinned according to the
    293   ** discardChance setting.  If discardChance is 0, the random discard
    294   ** never happens.  If discardChance is 100, it always happens.
    295   */
    296   if( p->bPurgeable
    297   && (100-testpcacheGlobal.discardChance) <= (testpcacheRandom(p)%100)
    298   ){
    299     discard = 1;
    300   }
    301 
    302   for(i=0; i<TESTPCACHE_NPAGE; i++){
    303     if( p->a[i].pData==pOldPage ){
    304       /* The pOldPage pointer always points to a pinned page */
    305       assert( p->a[i].isPinned );
    306       p->a[i].isPinned = 0;
    307       p->nPinned--;
    308       assert( p->nPinned>=0 );
    309       if( discard ){
    310         p->a[i].key = 0;
    311         p->nFree++;
    312         assert( p->nFree<=TESTPCACHE_NPAGE );
    313       }
    314       return;
    315     }
    316   }
    317 
    318   /* The pOldPage pointer always points to a valid page */
    319   assert( 0 );
    320 }
    321 
    322 
    323 /*
    324 ** Rekey a single page.
    325 */
    326 static void testpcacheRekey(
    327   sqlite3_pcache *pCache,
    328   void *pOldPage,
    329   unsigned oldKey,
    330   unsigned newKey
    331 ){
    332   testpcache *p = (testpcache*)pCache;
    333   int i;
    334   assert( p->iMagic==TESTPCACHE_VALID );
    335   assert( testpcacheGlobal.pDummy!=0 );
    336   assert( testpcacheGlobal.nInstance>0 );
    337 
    338   /* If there already exists another page at newKey, verify that
    339   ** the other page is unpinned and discard it.
    340   */
    341   for(i=0; i<TESTPCACHE_NPAGE; i++){
    342     if( p->a[i].key==newKey ){
    343       /* The new key is never a page that is already pinned */
    344       assert( p->a[i].isPinned==0 );
    345       p->a[i].key = 0;
    346       p->nFree++;
    347       assert( p->nFree<=TESTPCACHE_NPAGE );
    348       break;
    349     }
    350   }
    351 
    352   /* Find the page to be rekeyed and rekey it.
    353   */
    354   for(i=0; i<TESTPCACHE_NPAGE; i++){
    355     if( p->a[i].key==oldKey ){
    356       /* The oldKey and pOldPage parameters match */
    357       assert( p->a[i].pData==pOldPage );
    358       /* Page to be rekeyed must be pinned */
    359       assert( p->a[i].isPinned );
    360       p->a[i].key = newKey;
    361       return;
    362     }
    363   }
    364 
    365   /* Rekey is always given a valid page to work with */
    366   assert( 0 );
    367 }
    368 
    369 
    370 /*
    371 ** Truncate the page cache.  Every page with a key of iLimit or larger
    372 ** is discarded.
    373 */
    374 static void testpcacheTruncate(sqlite3_pcache *pCache, unsigned iLimit){
    375   testpcache *p = (testpcache*)pCache;
    376   unsigned int i;
    377   assert( p->iMagic==TESTPCACHE_VALID );
    378   assert( testpcacheGlobal.pDummy!=0 );
    379   assert( testpcacheGlobal.nInstance>0 );
    380   for(i=0; i<TESTPCACHE_NPAGE; i++){
    381     if( p->a[i].key>=iLimit ){
    382       p->a[i].key = 0;
    383       if( p->a[i].isPinned ){
    384         p->nPinned--;
    385         assert( p->nPinned>=0 );
    386       }
    387       p->nFree++;
    388       assert( p->nFree<=TESTPCACHE_NPAGE );
    389     }
    390   }
    391 }
    392 
    393 /*
    394 ** Destroy a page cache.
    395 */
    396 static void testpcacheDestroy(sqlite3_pcache *pCache){
    397   testpcache *p = (testpcache*)pCache;
    398   assert( p->iMagic==TESTPCACHE_VALID );
    399   assert( testpcacheGlobal.pDummy!=0 );
    400   assert( testpcacheGlobal.nInstance>0 );
    401   p->iMagic = TESTPCACHE_CLEAR;
    402   sqlite3_free(p);
    403   testpcacheGlobal.nInstance--;
    404 }
    405 
    406 
    407 /*
    408 ** Invoke this routine to register or unregister the testing pager cache
    409 ** implemented by this file.
    410 **
    411 ** Install the test pager cache if installFlag is 1 and uninstall it if
    412 ** installFlag is 0.
    413 **
    414 ** When installing, discardChance is a number between 0 and 100 that
    415 ** indicates the probability of discarding a page when unpinning the
    416 ** page.  0 means never discard (unless the discard flag is set).
    417 ** 100 means always discard.
    418 */
    419 void installTestPCache(
    420   int installFlag,            /* True to install.  False to uninstall. */
    421   unsigned discardChance,     /* 0-100.  Chance to discard on unpin */
    422   unsigned prngSeed,          /* Seed for the PRNG */
    423   unsigned highStress         /* Call xStress agressively */
    424 ){
    425   static const sqlite3_pcache_methods testPcache = {
    426     (void*)&testpcacheGlobal,
    427     testpcacheInit,
    428     testpcacheShutdown,
    429     testpcacheCreate,
    430     testpcacheCachesize,
    431     testpcachePagecount,
    432     testpcacheFetch,
    433     testpcacheUnpin,
    434     testpcacheRekey,
    435     testpcacheTruncate,
    436     testpcacheDestroy,
    437   };
    438   static sqlite3_pcache_methods defaultPcache;
    439   static int isInstalled = 0;
    440 
    441   assert( testpcacheGlobal.nInstance==0 );
    442   assert( testpcacheGlobal.pDummy==0 );
    443   assert( discardChance<=100 );
    444   testpcacheGlobal.discardChance = discardChance;
    445   testpcacheGlobal.prngSeed = prngSeed ^ (prngSeed<<16);
    446   testpcacheGlobal.highStress = highStress;
    447   if( installFlag!=isInstalled ){
    448     if( installFlag ){
    449       sqlite3_config(SQLITE_CONFIG_GETPCACHE, &defaultPcache);
    450       assert( defaultPcache.xCreate!=testpcacheCreate );
    451       sqlite3_config(SQLITE_CONFIG_PCACHE, &testPcache);
    452     }else{
    453       assert( defaultPcache.xCreate!=0 );
    454       sqlite3_config(SQLITE_CONFIG_PCACHE, &defaultPcache);
    455     }
    456     isInstalled = installFlag;
    457   }
    458 }
    459