| OLD | NEW | 
 | (Empty) | 
|    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 ** $Id: test_pcache.c,v 1.3 2009/04/11 11:38:54 drh Exp $ |  | 
|   25 */ |  | 
|   26 #include "sqlite3.h" |  | 
|   27 #include <string.h> |  | 
|   28 #include <assert.h> |  | 
|   29  |  | 
|   30 /* |  | 
|   31 ** Global data used by this test implementation.  There is no |  | 
|   32 ** mutexing, which means this page cache will not work in a |  | 
|   33 ** multi-threaded test. |  | 
|   34 */ |  | 
|   35 typedef struct testpcacheGlobalType testpcacheGlobalType; |  | 
|   36 struct testpcacheGlobalType { |  | 
|   37   void *pDummy;             /* Dummy allocation to simulate failures */ |  | 
|   38   int nInstance;            /* Number of current instances */ |  | 
|   39   unsigned discardChance;   /* Chance of discarding on an unpin (0-100) */ |  | 
|   40   unsigned prngSeed;        /* Seed for the PRNG */ |  | 
|   41   unsigned highStress;      /* Call xStress agressively */ |  | 
|   42 }; |  | 
|   43 static testpcacheGlobalType testpcacheGlobal; |  | 
|   44  |  | 
|   45 /* |  | 
|   46 ** Initializer. |  | 
|   47 ** |  | 
|   48 ** Verify that the initializer is only called when the system is |  | 
|   49 ** uninitialized.  Allocate some memory and report SQLITE_NOMEM if |  | 
|   50 ** the allocation fails.  This provides a means to test the recovery |  | 
|   51 ** from a failed initialization attempt.  It also verifies that the |  | 
|   52 ** the destructor always gets call - otherwise there would be a |  | 
|   53 ** memory leak. |  | 
|   54 */ |  | 
|   55 static int testpcacheInit(void *pArg){ |  | 
|   56   assert( pArg==(void*)&testpcacheGlobal ); |  | 
|   57   assert( testpcacheGlobal.pDummy==0 ); |  | 
|   58   assert( testpcacheGlobal.nInstance==0 ); |  | 
|   59   testpcacheGlobal.pDummy = sqlite3_malloc(10); |  | 
|   60   return testpcacheGlobal.pDummy==0 ? SQLITE_NOMEM : SQLITE_OK; |  | 
|   61 } |  | 
|   62  |  | 
|   63 /* |  | 
|   64 ** Destructor |  | 
|   65 ** |  | 
|   66 ** Verify that this is only called after initialization. |  | 
|   67 ** Free the memory allocated by the initializer. |  | 
|   68 */ |  | 
|   69 static void testpcacheShutdown(void *pArg){ |  | 
|   70   assert( pArg==(void*)&testpcacheGlobal ); |  | 
|   71   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|   72   assert( testpcacheGlobal.nInstance==0 ); |  | 
|   73   sqlite3_free( testpcacheGlobal.pDummy ); |  | 
|   74   testpcacheGlobal.pDummy = 0; |  | 
|   75 } |  | 
|   76  |  | 
|   77 /* |  | 
|   78 ** Number of pages in a cache. |  | 
|   79 ** |  | 
|   80 ** The number of pages is a hard upper bound in this test module. |  | 
|   81 ** If more pages are requested, sqlite3PcacheFetch() returns NULL. |  | 
|   82 ** |  | 
|   83 ** If testing with in-memory temp tables, provide a larger pcache. |  | 
|   84 ** Some of the test cases need this. |  | 
|   85 */ |  | 
|   86 #if defined(SQLITE_TEMP_STORE) && SQLITE_TEMP_STORE>=2 |  | 
|   87 # define TESTPCACHE_NPAGE    499 |  | 
|   88 #else |  | 
|   89 # define TESTPCACHE_NPAGE    217 |  | 
|   90 #endif |  | 
|   91 #define TESTPCACHE_RESERVE   17 |  | 
|   92  |  | 
|   93 /* |  | 
|   94 ** Magic numbers used to determine validity of the page cache. |  | 
|   95 */ |  | 
|   96 #define TESTPCACHE_VALID  0x364585fd |  | 
|   97 #define TESTPCACHE_CLEAR  0xd42670d4 |  | 
|   98  |  | 
|   99 /* |  | 
|  100 ** Private implementation of a page cache. |  | 
|  101 */ |  | 
|  102 typedef struct testpcache testpcache; |  | 
|  103 struct testpcache { |  | 
|  104   int szPage;               /* Size of each page.  Multiple of 8. */ |  | 
|  105   int bPurgeable;           /* True if the page cache is purgeable */ |  | 
|  106   int nFree;                /* Number of unused slots in a[] */ |  | 
|  107   int nPinned;              /* Number of pinned slots in a[] */ |  | 
|  108   unsigned iRand;           /* State of the PRNG */ |  | 
|  109   unsigned iMagic;          /* Magic number for sanity checking */ |  | 
|  110   struct testpcachePage { |  | 
|  111     unsigned key;              /* The key for this page. 0 means unallocated */ |  | 
|  112     int isPinned;              /* True if the page is pinned */ |  | 
|  113     void *pData;               /* Data for this page */ |  | 
|  114   } a[TESTPCACHE_NPAGE];    /* All pages in the cache */ |  | 
|  115 }; |  | 
|  116  |  | 
|  117 /* |  | 
|  118 ** Get a random number using the PRNG in the given page cache. |  | 
|  119 */ |  | 
|  120 static unsigned testpcacheRandom(testpcache *p){ |  | 
|  121   unsigned x = 0; |  | 
|  122   int i; |  | 
|  123   for(i=0; i<4; i++){ |  | 
|  124     p->iRand = (p->iRand*69069 + 5); |  | 
|  125     x = (x<<8) | ((p->iRand>>16)&0xff); |  | 
|  126   } |  | 
|  127   return x; |  | 
|  128 } |  | 
|  129  |  | 
|  130  |  | 
|  131 /* |  | 
|  132 ** Allocate a new page cache instance. |  | 
|  133 */ |  | 
|  134 static sqlite3_pcache *testpcacheCreate(int szPage, int bPurgeable){ |  | 
|  135   int nMem; |  | 
|  136   char *x; |  | 
|  137   testpcache *p; |  | 
|  138   int i; |  | 
|  139   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  140   szPage = (szPage+7)&~7; |  | 
|  141   nMem = sizeof(testpcache) + TESTPCACHE_NPAGE*szPage; |  | 
|  142   p = sqlite3_malloc( nMem ); |  | 
|  143   if( p==0 ) return 0; |  | 
|  144   x = (char*)&p[1]; |  | 
|  145   p->szPage = szPage; |  | 
|  146   p->nFree = TESTPCACHE_NPAGE; |  | 
|  147   p->nPinned = 0; |  | 
|  148   p->iRand = testpcacheGlobal.prngSeed; |  | 
|  149   p->bPurgeable = bPurgeable; |  | 
|  150   p->iMagic = TESTPCACHE_VALID; |  | 
|  151   for(i=0; i<TESTPCACHE_NPAGE; i++, x += szPage){ |  | 
|  152     p->a[i].key = 0; |  | 
|  153     p->a[i].isPinned = 0; |  | 
|  154     p->a[i].pData = (void*)x; |  | 
|  155   } |  | 
|  156   testpcacheGlobal.nInstance++; |  | 
|  157   return (sqlite3_pcache*)p; |  | 
|  158 } |  | 
|  159  |  | 
|  160 /* |  | 
|  161 ** Set the cache size |  | 
|  162 */ |  | 
|  163 static void testpcacheCachesize(sqlite3_pcache *pCache, int newSize){ |  | 
|  164   testpcache *p = (testpcache*)pCache; |  | 
|  165   assert( p->iMagic==TESTPCACHE_VALID ); |  | 
|  166   assert( newSize>=1 ); |  | 
|  167   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  168   assert( testpcacheGlobal.nInstance>0 ); |  | 
|  169 } |  | 
|  170  |  | 
|  171 /* |  | 
|  172 ** Return the number of pages in the cache that are being used. |  | 
|  173 ** This includes both pinned and unpinned pages. |  | 
|  174 */ |  | 
|  175 static int testpcachePagecount(sqlite3_pcache *pCache){ |  | 
|  176   testpcache *p = (testpcache*)pCache; |  | 
|  177   assert( p->iMagic==TESTPCACHE_VALID ); |  | 
|  178   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  179   assert( testpcacheGlobal.nInstance>0 ); |  | 
|  180   return TESTPCACHE_NPAGE - p->nFree; |  | 
|  181 } |  | 
|  182  |  | 
|  183 /* |  | 
|  184 ** Fetch a page. |  | 
|  185 */ |  | 
|  186 static void *testpcacheFetch( |  | 
|  187   sqlite3_pcache *pCache, |  | 
|  188   unsigned key, |  | 
|  189   int createFlag |  | 
|  190 ){ |  | 
|  191   testpcache *p = (testpcache*)pCache; |  | 
|  192   int i, j; |  | 
|  193   assert( p->iMagic==TESTPCACHE_VALID ); |  | 
|  194   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  195   assert( testpcacheGlobal.nInstance>0 ); |  | 
|  196  |  | 
|  197   /* See if the page is already in cache.  Return immediately if it is */ |  | 
|  198   for(i=0; i<TESTPCACHE_NPAGE; i++){ |  | 
|  199     if( p->a[i].key==key ){ |  | 
|  200       if( !p->a[i].isPinned ){ |  | 
|  201         p->nPinned++; |  | 
|  202         assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); |  | 
|  203         p->a[i].isPinned = 1; |  | 
|  204       } |  | 
|  205       return p->a[i].pData; |  | 
|  206     } |  | 
|  207   } |  | 
|  208  |  | 
|  209   /* If createFlag is 0, never allocate a new page */ |  | 
|  210   if( createFlag==0 ){ |  | 
|  211     return 0; |  | 
|  212   } |  | 
|  213  |  | 
|  214   /* If no pages are available, always fail */ |  | 
|  215   if( p->nPinned==TESTPCACHE_NPAGE ){ |  | 
|  216     return 0; |  | 
|  217   } |  | 
|  218  |  | 
|  219   /* Do not allocate the last TESTPCACHE_RESERVE pages unless createFlag is 2 */ |  | 
|  220   if( p->nPinned>=TESTPCACHE_NPAGE-TESTPCACHE_RESERVE && createFlag<2 ){ |  | 
|  221     return 0; |  | 
|  222   } |  | 
|  223  |  | 
|  224   /* Do not allocate if highStress is enabled and createFlag is not 2.   |  | 
|  225   ** |  | 
|  226   ** The highStress setting causes pagerStress() to be called much more |  | 
|  227   ** often, which exercises the pager logic more intensely. |  | 
|  228   */ |  | 
|  229   if( testpcacheGlobal.highStress && createFlag<2 ){ |  | 
|  230     return 0; |  | 
|  231   } |  | 
|  232  |  | 
|  233   /* Find a free page to allocate if there are any free pages. |  | 
|  234   ** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2. |  | 
|  235   */ |  | 
|  236   if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){ |  | 
|  237     j = testpcacheRandom(p) % TESTPCACHE_NPAGE; |  | 
|  238     for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ |  | 
|  239       if( p->a[j].key==0 ){ |  | 
|  240         p->a[j].key = key; |  | 
|  241         p->a[j].isPinned = 1; |  | 
|  242         memset(p->a[j].pData, 0, p->szPage); |  | 
|  243         p->nPinned++; |  | 
|  244         p->nFree--; |  | 
|  245         assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); |  | 
|  246         return p->a[j].pData; |  | 
|  247       } |  | 
|  248     } |  | 
|  249  |  | 
|  250     /* The prior loop always finds a freepage to allocate */ |  | 
|  251     assert( 0 ); |  | 
|  252   } |  | 
|  253  |  | 
|  254   /* If this cache is not purgeable then we have to fail. |  | 
|  255   */ |  | 
|  256   if( p->bPurgeable==0 ){ |  | 
|  257     return 0; |  | 
|  258   } |  | 
|  259  |  | 
|  260   /* If there are no free pages, recycle a page.  The page to |  | 
|  261   ** recycle is selected at random from all unpinned pages. |  | 
|  262   */ |  | 
|  263   j = testpcacheRandom(p) % TESTPCACHE_NPAGE; |  | 
|  264   for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ |  | 
|  265     if( p->a[j].key>0 && p->a[j].isPinned==0 ){ |  | 
|  266       p->a[j].key = key; |  | 
|  267       p->a[j].isPinned = 1; |  | 
|  268       memset(p->a[j].pData, 0, p->szPage); |  | 
|  269       p->nPinned++; |  | 
|  270       assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); |  | 
|  271       return p->a[j].pData; |  | 
|  272     } |  | 
|  273   } |  | 
|  274  |  | 
|  275   /* The previous loop always finds a page to recycle. */ |  | 
|  276   assert(0); |  | 
|  277   return 0; |  | 
|  278 } |  | 
|  279  |  | 
|  280 /* |  | 
|  281 ** Unpin a page. |  | 
|  282 */ |  | 
|  283 static void testpcacheUnpin( |  | 
|  284   sqlite3_pcache *pCache, |  | 
|  285   void *pOldPage, |  | 
|  286   int discard |  | 
|  287 ){ |  | 
|  288   testpcache *p = (testpcache*)pCache; |  | 
|  289   int i; |  | 
|  290   assert( p->iMagic==TESTPCACHE_VALID ); |  | 
|  291   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  292   assert( testpcacheGlobal.nInstance>0 ); |  | 
|  293  |  | 
|  294   /* Randomly discard pages as they are unpinned according to the |  | 
|  295   ** discardChance setting.  If discardChance is 0, the random discard |  | 
|  296   ** never happens.  If discardChance is 100, it always happens. |  | 
|  297   */ |  | 
|  298   if( p->bPurgeable |  | 
|  299   && (100-testpcacheGlobal.discardChance) <= (testpcacheRandom(p)%100) |  | 
|  300   ){ |  | 
|  301     discard = 1; |  | 
|  302   } |  | 
|  303  |  | 
|  304   for(i=0; i<TESTPCACHE_NPAGE; i++){ |  | 
|  305     if( p->a[i].pData==pOldPage ){ |  | 
|  306       /* The pOldPage pointer always points to a pinned page */ |  | 
|  307       assert( p->a[i].isPinned ); |  | 
|  308       p->a[i].isPinned = 0; |  | 
|  309       p->nPinned--; |  | 
|  310       assert( p->nPinned>=0 ); |  | 
|  311       if( discard ){ |  | 
|  312         p->a[i].key = 0; |  | 
|  313         p->nFree++; |  | 
|  314         assert( p->nFree<=TESTPCACHE_NPAGE ); |  | 
|  315       } |  | 
|  316       return; |  | 
|  317     } |  | 
|  318   } |  | 
|  319  |  | 
|  320   /* The pOldPage pointer always points to a valid page */ |  | 
|  321   assert( 0 ); |  | 
|  322 } |  | 
|  323  |  | 
|  324  |  | 
|  325 /* |  | 
|  326 ** Rekey a single page. |  | 
|  327 */ |  | 
|  328 static void testpcacheRekey( |  | 
|  329   sqlite3_pcache *pCache, |  | 
|  330   void *pOldPage, |  | 
|  331   unsigned oldKey, |  | 
|  332   unsigned newKey |  | 
|  333 ){ |  | 
|  334   testpcache *p = (testpcache*)pCache; |  | 
|  335   int i; |  | 
|  336   assert( p->iMagic==TESTPCACHE_VALID ); |  | 
|  337   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  338   assert( testpcacheGlobal.nInstance>0 ); |  | 
|  339  |  | 
|  340   /* If there already exists another page at newKey, verify that |  | 
|  341   ** the other page is unpinned and discard it. |  | 
|  342   */ |  | 
|  343   for(i=0; i<TESTPCACHE_NPAGE; i++){ |  | 
|  344     if( p->a[i].key==newKey ){ |  | 
|  345       /* The new key is never a page that is already pinned */ |  | 
|  346       assert( p->a[i].isPinned==0 ); |  | 
|  347       p->a[i].key = 0; |  | 
|  348       p->nFree++; |  | 
|  349       assert( p->nFree<=TESTPCACHE_NPAGE ); |  | 
|  350       break; |  | 
|  351     } |  | 
|  352   } |  | 
|  353  |  | 
|  354   /* Find the page to be rekeyed and rekey it. |  | 
|  355   */ |  | 
|  356   for(i=0; i<TESTPCACHE_NPAGE; i++){ |  | 
|  357     if( p->a[i].key==oldKey ){ |  | 
|  358       /* The oldKey and pOldPage parameters match */ |  | 
|  359       assert( p->a[i].pData==pOldPage ); |  | 
|  360       /* Page to be rekeyed must be pinned */ |  | 
|  361       assert( p->a[i].isPinned ); |  | 
|  362       p->a[i].key = newKey; |  | 
|  363       return; |  | 
|  364     } |  | 
|  365   } |  | 
|  366  |  | 
|  367   /* Rekey is always given a valid page to work with */ |  | 
|  368   assert( 0 ); |  | 
|  369 } |  | 
|  370  |  | 
|  371  |  | 
|  372 /* |  | 
|  373 ** Truncate the page cache.  Every page with a key of iLimit or larger |  | 
|  374 ** is discarded. |  | 
|  375 */ |  | 
|  376 static void testpcacheTruncate(sqlite3_pcache *pCache, unsigned iLimit){ |  | 
|  377   testpcache *p = (testpcache*)pCache; |  | 
|  378   unsigned int i; |  | 
|  379   assert( p->iMagic==TESTPCACHE_VALID ); |  | 
|  380   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  381   assert( testpcacheGlobal.nInstance>0 ); |  | 
|  382   for(i=0; i<TESTPCACHE_NPAGE; i++){ |  | 
|  383     if( p->a[i].key>=iLimit ){ |  | 
|  384       p->a[i].key = 0; |  | 
|  385       if( p->a[i].isPinned ){ |  | 
|  386         p->nPinned--; |  | 
|  387         assert( p->nPinned>=0 ); |  | 
|  388       } |  | 
|  389       p->nFree++; |  | 
|  390       assert( p->nFree<=TESTPCACHE_NPAGE ); |  | 
|  391     } |  | 
|  392   } |  | 
|  393 } |  | 
|  394  |  | 
|  395 /* |  | 
|  396 ** Destroy a page cache. |  | 
|  397 */ |  | 
|  398 static void testpcacheDestroy(sqlite3_pcache *pCache){ |  | 
|  399   testpcache *p = (testpcache*)pCache; |  | 
|  400   assert( p->iMagic==TESTPCACHE_VALID ); |  | 
|  401   assert( testpcacheGlobal.pDummy!=0 ); |  | 
|  402   assert( testpcacheGlobal.nInstance>0 ); |  | 
|  403   p->iMagic = TESTPCACHE_CLEAR; |  | 
|  404   sqlite3_free(p); |  | 
|  405   testpcacheGlobal.nInstance--; |  | 
|  406 } |  | 
|  407  |  | 
|  408  |  | 
|  409 /* |  | 
|  410 ** Invoke this routine to register or unregister the testing pager cache |  | 
|  411 ** implemented by this file. |  | 
|  412 ** |  | 
|  413 ** Install the test pager cache if installFlag is 1 and uninstall it if |  | 
|  414 ** installFlag is 0. |  | 
|  415 ** |  | 
|  416 ** When installing, discardChance is a number between 0 and 100 that |  | 
|  417 ** indicates the probability of discarding a page when unpinning the |  | 
|  418 ** page.  0 means never discard (unless the discard flag is set). |  | 
|  419 ** 100 means always discard. |  | 
|  420 */ |  | 
|  421 void installTestPCache( |  | 
|  422   int installFlag,            /* True to install.  False to uninstall. */ |  | 
|  423   unsigned discardChance,     /* 0-100.  Chance to discard on unpin */ |  | 
|  424   unsigned prngSeed,          /* Seed for the PRNG */ |  | 
|  425   unsigned highStress         /* Call xStress agressively */ |  | 
|  426 ){ |  | 
|  427   static const sqlite3_pcache_methods testPcache = { |  | 
|  428     (void*)&testpcacheGlobal, |  | 
|  429     testpcacheInit, |  | 
|  430     testpcacheShutdown, |  | 
|  431     testpcacheCreate, |  | 
|  432     testpcacheCachesize, |  | 
|  433     testpcachePagecount, |  | 
|  434     testpcacheFetch, |  | 
|  435     testpcacheUnpin, |  | 
|  436     testpcacheRekey, |  | 
|  437     testpcacheTruncate, |  | 
|  438     testpcacheDestroy, |  | 
|  439   }; |  | 
|  440   static sqlite3_pcache_methods defaultPcache; |  | 
|  441   static int isInstalled = 0; |  | 
|  442  |  | 
|  443   assert( testpcacheGlobal.nInstance==0 ); |  | 
|  444   assert( testpcacheGlobal.pDummy==0 ); |  | 
|  445   assert( discardChance<=100 ); |  | 
|  446   testpcacheGlobal.discardChance = discardChance; |  | 
|  447   testpcacheGlobal.prngSeed = prngSeed ^ (prngSeed<<16); |  | 
|  448   testpcacheGlobal.highStress = highStress; |  | 
|  449   if( installFlag!=isInstalled ){ |  | 
|  450     if( installFlag ){ |  | 
|  451       sqlite3_config(SQLITE_CONFIG_GETPCACHE, &defaultPcache); |  | 
|  452       assert( defaultPcache.xCreate!=testpcacheCreate ); |  | 
|  453       sqlite3_config(SQLITE_CONFIG_PCACHE, &testPcache); |  | 
|  454     }else{ |  | 
|  455       assert( defaultPcache.xCreate!=0 ); |  | 
|  456       sqlite3_config(SQLITE_CONFIG_PCACHE, &defaultPcache); |  | 
|  457     } |  | 
|  458     isInstalled = installFlag; |  | 
|  459   } |  | 
|  460 } |  | 
| OLD | NEW |