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