| /* | |
| ** 2008 November 18 | |
| ** | |
| ** The author disclaims copyright to this source code. In place of | |
| ** a legal notice, here is a blessing: | |
| ** | |
| ** May you do good and not evil. | |
| ** May you find forgiveness for yourself and forgive others. | |
| ** May you share freely, never taking more than you give. | |
| ** | |
| ************************************************************************* | |
| ** | |
| ** This file contains code used for testing the SQLite system. | |
| ** None of the code in this file goes into a deliverable build. | |
| ** | |
| ** This file contains an application-defined pager cache | |
| ** implementation that can be plugged in in place of the | |
| ** default pcache. This alternative pager cache will throw | |
| ** some errors that the default cache does not. | |
| ** | |
| ** This pagecache implementation is designed for simplicity | |
| ** not speed. | |
| */ | |
| #include "sqlite3.h" | |
| #include <string.h> | |
| #include <assert.h> | |
| /* | |
| ** Global data used by this test implementation. There is no | |
| ** mutexing, which means this page cache will not work in a | |
| ** multi-threaded test. | |
| */ | |
| typedef struct testpcacheGlobalType testpcacheGlobalType; | |
| struct testpcacheGlobalType { | |
| void *pDummy; /* Dummy allocation to simulate failures */ | |
| int nInstance; /* Number of current instances */ | |
| unsigned discardChance; /* Chance of discarding on an unpin (0-100) */ | |
| unsigned prngSeed; /* Seed for the PRNG */ | |
| unsigned highStress; /* Call xStress agressively */ | |
| }; | |
| static testpcacheGlobalType testpcacheGlobal; | |
| /* | |
| ** Initializer. | |
| ** | |
| ** Verify that the initializer is only called when the system is | |
| ** uninitialized. Allocate some memory and report SQLITE_NOMEM if | |
| ** the allocation fails. This provides a means to test the recovery | |
| ** from a failed initialization attempt. It also verifies that the | |
| ** the destructor always gets call - otherwise there would be a | |
| ** memory leak. | |
| */ | |
| static int testpcacheInit(void *pArg){ | |
| assert( pArg==(void*)&testpcacheGlobal ); | |
| assert( testpcacheGlobal.pDummy==0 ); | |
| assert( testpcacheGlobal.nInstance==0 ); | |
| testpcacheGlobal.pDummy = sqlite3_malloc(10); | |
| return testpcacheGlobal.pDummy==0 ? SQLITE_NOMEM : SQLITE_OK; | |
| } | |
| /* | |
| ** Destructor | |
| ** | |
| ** Verify that this is only called after initialization. | |
| ** Free the memory allocated by the initializer. | |
| */ | |
| static void testpcacheShutdown(void *pArg){ | |
| assert( pArg==(void*)&testpcacheGlobal ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance==0 ); | |
| sqlite3_free( testpcacheGlobal.pDummy ); | |
| testpcacheGlobal.pDummy = 0; | |
| } | |
| /* | |
| ** Number of pages in a cache. | |
| ** | |
| ** The number of pages is a hard upper bound in this test module. | |
| ** If more pages are requested, sqlite3PcacheFetch() returns NULL. | |
| ** | |
| ** If testing with in-memory temp tables, provide a larger pcache. | |
| ** Some of the test cases need this. | |
| */ | |
| #if defined(SQLITE_TEMP_STORE) && SQLITE_TEMP_STORE>=2 | |
| # define TESTPCACHE_NPAGE 499 | |
| #else | |
| # define TESTPCACHE_NPAGE 217 | |
| #endif | |
| #define TESTPCACHE_RESERVE 17 | |
| /* | |
| ** Magic numbers used to determine validity of the page cache. | |
| */ | |
| #define TESTPCACHE_VALID 0x364585fd | |
| #define TESTPCACHE_CLEAR 0xd42670d4 | |
| /* | |
| ** Private implementation of a page cache. | |
| */ | |
| typedef struct testpcache testpcache; | |
| struct testpcache { | |
| int szPage; /* Size of each page. Multiple of 8. */ | |
| int szExtra; /* Size of extra data that accompanies each page */ | |
| int bPurgeable; /* True if the page cache is purgeable */ | |
| int nFree; /* Number of unused slots in a[] */ | |
| int nPinned; /* Number of pinned slots in a[] */ | |
| unsigned iRand; /* State of the PRNG */ | |
| unsigned iMagic; /* Magic number for sanity checking */ | |
| struct testpcachePage { | |
| sqlite3_pcache_page page; /* Base class */ | |
| unsigned key; /* The key for this page. 0 means unallocated */ | |
| int isPinned; /* True if the page is pinned */ | |
| } a[TESTPCACHE_NPAGE]; /* All pages in the cache */ | |
| }; | |
| /* | |
| ** Get a random number using the PRNG in the given page cache. | |
| */ | |
| static unsigned testpcacheRandom(testpcache *p){ | |
| unsigned x = 0; | |
| int i; | |
| for(i=0; i<4; i++){ | |
| p->iRand = (p->iRand*69069 + 5); | |
| x = (x<<8) | ((p->iRand>>16)&0xff); | |
| } | |
| return x; | |
| } | |
| /* | |
| ** Allocate a new page cache instance. | |
| */ | |
| static sqlite3_pcache *testpcacheCreate( | |
| int szPage, | |
| int szExtra, | |
| int bPurgeable | |
| ){ | |
| int nMem; | |
| char *x; | |
| testpcache *p; | |
| int i; | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| szPage = (szPage+7)&~7; | |
| nMem = sizeof(testpcache) + TESTPCACHE_NPAGE*(szPage+szExtra); | |
| p = sqlite3_malloc( nMem ); | |
| if( p==0 ) return 0; | |
| x = (char*)&p[1]; | |
| p->szPage = szPage; | |
| p->szExtra = szExtra; | |
| p->nFree = TESTPCACHE_NPAGE; | |
| p->nPinned = 0; | |
| p->iRand = testpcacheGlobal.prngSeed; | |
| p->bPurgeable = bPurgeable; | |
| p->iMagic = TESTPCACHE_VALID; | |
| for(i=0; i<TESTPCACHE_NPAGE; i++, x += (szPage+szExtra)){ | |
| p->a[i].key = 0; | |
| p->a[i].isPinned = 0; | |
| p->a[i].page.pBuf = (void*)x; | |
| p->a[i].page.pExtra = (void*)&x[szPage]; | |
| } | |
| testpcacheGlobal.nInstance++; | |
| return (sqlite3_pcache*)p; | |
| } | |
| /* | |
| ** Set the cache size | |
| */ | |
| static void testpcacheCachesize(sqlite3_pcache *pCache, int newSize){ | |
| testpcache *p = (testpcache*)pCache; | |
| assert( p->iMagic==TESTPCACHE_VALID ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance>0 ); | |
| } | |
| /* | |
| ** Return the number of pages in the cache that are being used. | |
| ** This includes both pinned and unpinned pages. | |
| */ | |
| static int testpcachePagecount(sqlite3_pcache *pCache){ | |
| testpcache *p = (testpcache*)pCache; | |
| assert( p->iMagic==TESTPCACHE_VALID ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance>0 ); | |
| return TESTPCACHE_NPAGE - p->nFree; | |
| } | |
| /* | |
| ** Fetch a page. | |
| */ | |
| static sqlite3_pcache_page *testpcacheFetch( | |
| sqlite3_pcache *pCache, | |
| unsigned key, | |
| int createFlag | |
| ){ | |
| testpcache *p = (testpcache*)pCache; | |
| int i, j; | |
| assert( p->iMagic==TESTPCACHE_VALID ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance>0 ); | |
| /* See if the page is already in cache. Return immediately if it is */ | |
| for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| if( p->a[i].key==key ){ | |
| if( !p->a[i].isPinned ){ | |
| p->nPinned++; | |
| assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); | |
| p->a[i].isPinned = 1; | |
| } | |
| return &p->a[i].page; | |
| } | |
| } | |
| /* If createFlag is 0, never allocate a new page */ | |
| if( createFlag==0 ){ | |
| return 0; | |
| } | |
| /* If no pages are available, always fail */ | |
| if( p->nPinned==TESTPCACHE_NPAGE ){ | |
| return 0; | |
| } | |
| /* Do not allocate the last TESTPCACHE_RESERVE pages unless createFlag is 2 */ | |
| if( p->nPinned>=TESTPCACHE_NPAGE-TESTPCACHE_RESERVE && createFlag<2 ){ | |
| return 0; | |
| } | |
| /* Do not allocate if highStress is enabled and createFlag is not 2. | |
| ** | |
| ** The highStress setting causes pagerStress() to be called much more | |
| ** often, which exercises the pager logic more intensely. | |
| */ | |
| if( testpcacheGlobal.highStress && createFlag<2 ){ | |
| return 0; | |
| } | |
| /* Find a free page to allocate if there are any free pages. | |
| ** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2. | |
| */ | |
| if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){ | |
| j = testpcacheRandom(p) % TESTPCACHE_NPAGE; | |
| for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ | |
| if( p->a[j].key==0 ){ | |
| p->a[j].key = key; | |
| p->a[j].isPinned = 1; | |
| memset(p->a[j].page.pBuf, 0, p->szPage); | |
| memset(p->a[j].page.pExtra, 0, p->szExtra); | |
| p->nPinned++; | |
| p->nFree--; | |
| assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); | |
| return &p->a[j].page; | |
| } | |
| } | |
| /* The prior loop always finds a freepage to allocate */ | |
| assert( 0 ); | |
| } | |
| /* If this cache is not purgeable then we have to fail. | |
| */ | |
| if( p->bPurgeable==0 ){ | |
| return 0; | |
| } | |
| /* If there are no free pages, recycle a page. The page to | |
| ** recycle is selected at random from all unpinned pages. | |
| */ | |
| j = testpcacheRandom(p) % TESTPCACHE_NPAGE; | |
| for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){ | |
| if( p->a[j].key>0 && p->a[j].isPinned==0 ){ | |
| p->a[j].key = key; | |
| p->a[j].isPinned = 1; | |
| memset(p->a[j].page.pBuf, 0, p->szPage); | |
| memset(p->a[j].page.pExtra, 0, p->szExtra); | |
| p->nPinned++; | |
| assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree ); | |
| return &p->a[j].page; | |
| } | |
| } | |
| /* The previous loop always finds a page to recycle. */ | |
| assert(0); | |
| return 0; | |
| } | |
| /* | |
| ** Unpin a page. | |
| */ | |
| static void testpcacheUnpin( | |
| sqlite3_pcache *pCache, | |
| sqlite3_pcache_page *pOldPage, | |
| int discard | |
| ){ | |
| testpcache *p = (testpcache*)pCache; | |
| int i; | |
| assert( p->iMagic==TESTPCACHE_VALID ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance>0 ); | |
| /* Randomly discard pages as they are unpinned according to the | |
| ** discardChance setting. If discardChance is 0, the random discard | |
| ** never happens. If discardChance is 100, it always happens. | |
| */ | |
| if( p->bPurgeable | |
| && (100-testpcacheGlobal.discardChance) <= (testpcacheRandom(p)%100) | |
| ){ | |
| discard = 1; | |
| } | |
| for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| if( &p->a[i].page==pOldPage ){ | |
| /* The pOldPage pointer always points to a pinned page */ | |
| assert( p->a[i].isPinned ); | |
| p->a[i].isPinned = 0; | |
| p->nPinned--; | |
| assert( p->nPinned>=0 ); | |
| if( discard ){ | |
| p->a[i].key = 0; | |
| p->nFree++; | |
| assert( p->nFree<=TESTPCACHE_NPAGE ); | |
| } | |
| return; | |
| } | |
| } | |
| /* The pOldPage pointer always points to a valid page */ | |
| assert( 0 ); | |
| } | |
| /* | |
| ** Rekey a single page. | |
| */ | |
| static void testpcacheRekey( | |
| sqlite3_pcache *pCache, | |
| sqlite3_pcache_page *pOldPage, | |
| unsigned oldKey, | |
| unsigned newKey | |
| ){ | |
| testpcache *p = (testpcache*)pCache; | |
| int i; | |
| assert( p->iMagic==TESTPCACHE_VALID ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance>0 ); | |
| /* If there already exists another page at newKey, verify that | |
| ** the other page is unpinned and discard it. | |
| */ | |
| for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| if( p->a[i].key==newKey ){ | |
| /* The new key is never a page that is already pinned */ | |
| assert( p->a[i].isPinned==0 ); | |
| p->a[i].key = 0; | |
| p->nFree++; | |
| assert( p->nFree<=TESTPCACHE_NPAGE ); | |
| break; | |
| } | |
| } | |
| /* Find the page to be rekeyed and rekey it. | |
| */ | |
| for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| if( p->a[i].key==oldKey ){ | |
| /* The oldKey and pOldPage parameters match */ | |
| assert( &p->a[i].page==pOldPage ); | |
| /* Page to be rekeyed must be pinned */ | |
| assert( p->a[i].isPinned ); | |
| p->a[i].key = newKey; | |
| return; | |
| } | |
| } | |
| /* Rekey is always given a valid page to work with */ | |
| assert( 0 ); | |
| } | |
| /* | |
| ** Truncate the page cache. Every page with a key of iLimit or larger | |
| ** is discarded. | |
| */ | |
| static void testpcacheTruncate(sqlite3_pcache *pCache, unsigned iLimit){ | |
| testpcache *p = (testpcache*)pCache; | |
| unsigned int i; | |
| assert( p->iMagic==TESTPCACHE_VALID ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance>0 ); | |
| for(i=0; i<TESTPCACHE_NPAGE; i++){ | |
| if( p->a[i].key>=iLimit ){ | |
| p->a[i].key = 0; | |
| if( p->a[i].isPinned ){ | |
| p->nPinned--; | |
| assert( p->nPinned>=0 ); | |
| } | |
| p->nFree++; | |
| assert( p->nFree<=TESTPCACHE_NPAGE ); | |
| } | |
| } | |
| } | |
| /* | |
| ** Destroy a page cache. | |
| */ | |
| static void testpcacheDestroy(sqlite3_pcache *pCache){ | |
| testpcache *p = (testpcache*)pCache; | |
| assert( p->iMagic==TESTPCACHE_VALID ); | |
| assert( testpcacheGlobal.pDummy!=0 ); | |
| assert( testpcacheGlobal.nInstance>0 ); | |
| p->iMagic = TESTPCACHE_CLEAR; | |
| sqlite3_free(p); | |
| testpcacheGlobal.nInstance--; | |
| } | |
| /* | |
| ** Invoke this routine to register or unregister the testing pager cache | |
| ** implemented by this file. | |
| ** | |
| ** Install the test pager cache if installFlag is 1 and uninstall it if | |
| ** installFlag is 0. | |
| ** | |
| ** When installing, discardChance is a number between 0 and 100 that | |
| ** indicates the probability of discarding a page when unpinning the | |
| ** page. 0 means never discard (unless the discard flag is set). | |
| ** 100 means always discard. | |
| */ | |
| void installTestPCache( | |
| int installFlag, /* True to install. False to uninstall. */ | |
| unsigned discardChance, /* 0-100. Chance to discard on unpin */ | |
| unsigned prngSeed, /* Seed for the PRNG */ | |
| unsigned highStress /* Call xStress agressively */ | |
| ){ | |
| static const sqlite3_pcache_methods2 testPcache = { | |
| 1, | |
| (void*)&testpcacheGlobal, | |
| testpcacheInit, | |
| testpcacheShutdown, | |
| testpcacheCreate, | |
| testpcacheCachesize, | |
| testpcachePagecount, | |
| testpcacheFetch, | |
| testpcacheUnpin, | |
| testpcacheRekey, | |
| testpcacheTruncate, | |
| testpcacheDestroy, | |
| }; | |
| static sqlite3_pcache_methods2 defaultPcache; | |
| static int isInstalled = 0; | |
| assert( testpcacheGlobal.nInstance==0 ); | |
| assert( testpcacheGlobal.pDummy==0 ); | |
| assert( discardChance<=100 ); | |
| testpcacheGlobal.discardChance = discardChance; | |
| testpcacheGlobal.prngSeed = prngSeed ^ (prngSeed<<16); | |
| testpcacheGlobal.highStress = highStress; | |
| if( installFlag!=isInstalled ){ | |
| if( installFlag ){ | |
| sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &defaultPcache); | |
| assert( defaultPcache.xCreate!=testpcacheCreate ); | |
| sqlite3_config(SQLITE_CONFIG_PCACHE2, &testPcache); | |
| }else{ | |
| assert( defaultPcache.xCreate!=0 ); | |
| sqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultPcache); | |
| } | |
| isInstalled = installFlag; | |
| } | |
| } |