xref: /sqlite-3.40.0/src/test_pcache.c (revision d5578433)
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 }
468