modernc.org/cc@v1.0.1/v2/testdata/_sqlite/src/test_pcache.c (about)

     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  }