• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
testpcacheInit(void * pArg)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 */
testpcacheShutdown(void * pArg)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 */
testpcacheRandom(testpcache * p)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 */
testpcacheCreate(int szPage,int bPurgeable)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 */
testpcacheCachesize(sqlite3_pcache * pCache,int newSize)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 */
testpcachePagecount(sqlite3_pcache * pCache)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 */
testpcacheFetch(sqlite3_pcache * pCache,unsigned key,int createFlag)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 */
testpcacheUnpin(sqlite3_pcache * pCache,void * pOldPage,int discard)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 */
testpcacheRekey(sqlite3_pcache * pCache,void * pOldPage,unsigned oldKey,unsigned newKey)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 */
testpcacheTruncate(sqlite3_pcache * pCache,unsigned iLimit)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 */
testpcacheDestroy(sqlite3_pcache * pCache)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 */
installTestPCache(int installFlag,unsigned discardChance,unsigned prngSeed,unsigned highStress)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