xref: /sqlite-3.40.0/src/test_pcache.c (revision f2a84e3c)
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.2 2009/01/07 03:59:47 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 #define TESTPCACHE_NPAGE    217
81 #define TESTPCACHE_RESERVE   17
82 
83 /*
84 ** Magic numbers used to determine validity of the page cache.
85 */
86 #define TESTPCACHE_VALID  0x364585fd
87 #define TESTPCACHE_CLEAR  0xd42670d4
88 
89 /*
90 ** Private implementation of a page cache.
91 */
92 typedef struct testpcache testpcache;
93 struct testpcache {
94   int szPage;               /* Size of each page.  Multiple of 8. */
95   int bPurgeable;           /* True if the page cache is purgeable */
96   int nFree;                /* Number of unused slots in a[] */
97   int nPinned;              /* Number of pinned slots in a[] */
98   unsigned iRand;           /* State of the PRNG */
99   unsigned iMagic;          /* Magic number for sanity checking */
100   struct testpcachePage {
101     unsigned key;              /* The key for this page. 0 means unallocated */
102     int isPinned;              /* True if the page is pinned */
103     void *pData;               /* Data for this page */
104   } a[TESTPCACHE_NPAGE];    /* All pages in the cache */
105 };
106 
107 /*
108 ** Get a random number using the PRNG in the given page cache.
109 */
110 static unsigned testpcacheRandom(testpcache *p){
111   unsigned x = 0;
112   int i;
113   for(i=0; i<4; i++){
114     p->iRand = (p->iRand*69069 + 5);
115     x = (x<<8) | ((p->iRand>>16)&0xff);
116   }
117   return x;
118 }
119 
120 
121 /*
122 ** Allocate a new page cache instance.
123 */
124 static sqlite3_pcache *testpcacheCreate(int szPage, int bPurgeable){
125   int nMem;
126   char *x;
127   testpcache *p;
128   int i;
129   assert( testpcacheGlobal.pDummy!=0 );
130   szPage = (szPage+7)&~7;
131   nMem = sizeof(testpcache) + TESTPCACHE_NPAGE*szPage;
132   p = sqlite3_malloc( nMem );
133   if( p==0 ) return 0;
134   x = (char*)&p[1];
135   p->szPage = szPage;
136   p->nFree = TESTPCACHE_NPAGE;
137   p->nPinned = 0;
138   p->iRand = testpcacheGlobal.prngSeed;
139   p->bPurgeable = bPurgeable;
140   p->iMagic = TESTPCACHE_VALID;
141   for(i=0; i<TESTPCACHE_NPAGE; i++, x += szPage){
142     p->a[i].key = 0;
143     p->a[i].isPinned = 0;
144     p->a[i].pData = (void*)x;
145   }
146   testpcacheGlobal.nInstance++;
147   return (sqlite3_pcache*)p;
148 }
149 
150 /*
151 ** Set the cache size
152 */
153 static void testpcacheCachesize(sqlite3_pcache *pCache, int newSize){
154   testpcache *p = (testpcache*)pCache;
155   assert( p->iMagic==TESTPCACHE_VALID );
156   assert( newSize>=1 );
157   assert( testpcacheGlobal.pDummy!=0 );
158   assert( testpcacheGlobal.nInstance>0 );
159 }
160 
161 /*
162 ** Return the number of pages in the cache that are being used.
163 ** This includes both pinned and unpinned pages.
164 */
165 static int testpcachePagecount(sqlite3_pcache *pCache){
166   testpcache *p = (testpcache*)pCache;
167   assert( p->iMagic==TESTPCACHE_VALID );
168   assert( testpcacheGlobal.pDummy!=0 );
169   assert( testpcacheGlobal.nInstance>0 );
170   return TESTPCACHE_NPAGE - p->nFree;
171 }
172 
173 /*
174 ** Fetch a page.
175 */
176 static void *testpcacheFetch(
177   sqlite3_pcache *pCache,
178   unsigned key,
179   int createFlag
180 ){
181   testpcache *p = (testpcache*)pCache;
182   int i, j;
183   assert( p->iMagic==TESTPCACHE_VALID );
184   assert( testpcacheGlobal.pDummy!=0 );
185   assert( testpcacheGlobal.nInstance>0 );
186 
187   /* See if the page is already in cache.  Return immediately if it is */
188   for(i=0; i<TESTPCACHE_NPAGE; i++){
189     if( p->a[i].key==key ){
190       if( !p->a[i].isPinned ){
191         p->nPinned++;
192         assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
193         p->a[i].isPinned = 1;
194       }
195       return p->a[i].pData;
196     }
197   }
198 
199   /* If createFlag is 0, never allocate a new page */
200   if( createFlag==0 ){
201     return 0;
202   }
203 
204   /* If no pages are available, always fail */
205   if( p->nPinned==TESTPCACHE_NPAGE ){
206     return 0;
207   }
208 
209   /* Do not allocate the last TESTPCACHE_RESERVE pages unless createFlag is 2 */
210   if( p->nPinned>=TESTPCACHE_NPAGE-TESTPCACHE_RESERVE && createFlag<2 ){
211     return 0;
212   }
213 
214   /* Do not allocate if highStress is enabled and createFlag is not 2.
215   **
216   ** The highStress setting causes pagerStress() to be called much more
217   ** often, which exercises the pager logic more intensely.
218   */
219   if( testpcacheGlobal.highStress && createFlag<2 ){
220     return 0;
221   }
222 
223   /* Find a free page to allocate if there are any free pages.
224   ** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2.
225   */
226   if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){
227     j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
228     for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
229       if( p->a[j].key==0 ){
230         p->a[j].key = key;
231         p->a[j].isPinned = 1;
232         memset(p->a[j].pData, 0, p->szPage);
233         p->nPinned++;
234         p->nFree--;
235         assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
236         return p->a[j].pData;
237       }
238     }
239 
240     /* The prior loop always finds a freepage to allocate */
241     assert( 0 );
242   }
243 
244   /* If this cache is not purgeable then we have to fail.
245   */
246   if( p->bPurgeable==0 ){
247     return 0;
248   }
249 
250   /* If there are no free pages, recycle a page.  The page to
251   ** recycle is selected at random from all unpinned pages.
252   */
253   j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
254   for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
255     if( p->a[j].key>0 && p->a[j].isPinned==0 ){
256       p->a[j].key = key;
257       p->a[j].isPinned = 1;
258       memset(p->a[j].pData, 0, p->szPage);
259       p->nPinned++;
260       assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
261       return p->a[j].pData;
262     }
263   }
264 
265   /* The previous loop always finds a page to recycle. */
266   assert(0);
267   return 0;
268 }
269 
270 /*
271 ** Unpin a page.
272 */
273 static void testpcacheUnpin(
274   sqlite3_pcache *pCache,
275   void *pOldPage,
276   int discard
277 ){
278   testpcache *p = (testpcache*)pCache;
279   int i;
280   assert( p->iMagic==TESTPCACHE_VALID );
281   assert( testpcacheGlobal.pDummy!=0 );
282   assert( testpcacheGlobal.nInstance>0 );
283 
284   /* Randomly discard pages as they are unpinned according to the
285   ** discardChance setting.  If discardChance is 0, the random discard
286   ** never happens.  If discardChance is 100, it always happens.
287   */
288   if( p->bPurgeable
289   && (100-testpcacheGlobal.discardChance) <= (testpcacheRandom(p)%100)
290   ){
291     discard = 1;
292   }
293 
294   for(i=0; i<TESTPCACHE_NPAGE; i++){
295     if( p->a[i].pData==pOldPage ){
296       /* The pOldPage pointer always points to a pinned page */
297       assert( p->a[i].isPinned );
298       p->a[i].isPinned = 0;
299       p->nPinned--;
300       assert( p->nPinned>=0 );
301       if( discard ){
302         p->a[i].key = 0;
303         p->nFree++;
304         assert( p->nFree<=TESTPCACHE_NPAGE );
305       }
306       return;
307     }
308   }
309 
310   /* The pOldPage pointer always points to a valid page */
311   assert( 0 );
312 }
313 
314 
315 /*
316 ** Rekey a single page.
317 */
318 static void testpcacheRekey(
319   sqlite3_pcache *pCache,
320   void *pOldPage,
321   unsigned oldKey,
322   unsigned newKey
323 ){
324   testpcache *p = (testpcache*)pCache;
325   int i;
326   assert( p->iMagic==TESTPCACHE_VALID );
327   assert( testpcacheGlobal.pDummy!=0 );
328   assert( testpcacheGlobal.nInstance>0 );
329 
330   /* If there already exists another page at newKey, verify that
331   ** the other page is unpinned and discard it.
332   */
333   for(i=0; i<TESTPCACHE_NPAGE; i++){
334     if( p->a[i].key==newKey ){
335       /* The new key is never a page that is already pinned */
336       assert( p->a[i].isPinned==0 );
337       p->a[i].key = 0;
338       p->nFree++;
339       assert( p->nFree<=TESTPCACHE_NPAGE );
340       break;
341     }
342   }
343 
344   /* Find the page to be rekeyed and rekey it.
345   */
346   for(i=0; i<TESTPCACHE_NPAGE; i++){
347     if( p->a[i].key==oldKey ){
348       /* The oldKey and pOldPage parameters match */
349       assert( p->a[i].pData==pOldPage );
350       /* Page to be rekeyed must be pinned */
351       assert( p->a[i].isPinned );
352       p->a[i].key = newKey;
353       return;
354     }
355   }
356 
357   /* Rekey is always given a valid page to work with */
358   assert( 0 );
359 }
360 
361 
362 /*
363 ** Truncate the page cache.  Every page with a key of iLimit or larger
364 ** is discarded.
365 */
366 static void testpcacheTruncate(sqlite3_pcache *pCache, unsigned iLimit){
367   testpcache *p = (testpcache*)pCache;
368   unsigned int i;
369   assert( p->iMagic==TESTPCACHE_VALID );
370   assert( testpcacheGlobal.pDummy!=0 );
371   assert( testpcacheGlobal.nInstance>0 );
372   for(i=0; i<TESTPCACHE_NPAGE; i++){
373     if( p->a[i].key>=iLimit ){
374       p->a[i].key = 0;
375       if( p->a[i].isPinned ){
376         p->nPinned--;
377         assert( p->nPinned>=0 );
378       }
379       p->nFree++;
380       assert( p->nFree<=TESTPCACHE_NPAGE );
381     }
382   }
383 }
384 
385 /*
386 ** Destroy a page cache.
387 */
388 static void testpcacheDestroy(sqlite3_pcache *pCache){
389   testpcache *p = (testpcache*)pCache;
390   assert( p->iMagic==TESTPCACHE_VALID );
391   assert( testpcacheGlobal.pDummy!=0 );
392   assert( testpcacheGlobal.nInstance>0 );
393   p->iMagic = TESTPCACHE_CLEAR;
394   sqlite3_free(p);
395   testpcacheGlobal.nInstance--;
396 }
397 
398 
399 /*
400 ** Invoke this routine to register or unregister the testing pager cache
401 ** implemented by this file.
402 **
403 ** Install the test pager cache if installFlag is 1 and uninstall it if
404 ** installFlag is 0.
405 **
406 ** When installing, discardChance is a number between 0 and 100 that
407 ** indicates the probability of discarding a page when unpinning the
408 ** page.  0 means never discard (unless the discard flag is set).
409 ** 100 means always discard.
410 */
411 void installTestPCache(
412   int installFlag,            /* True to install.  False to uninstall. */
413   unsigned discardChance,     /* 0-100.  Chance to discard on unpin */
414   unsigned prngSeed,          /* Seed for the PRNG */
415   unsigned highStress         /* Call xStress agressively */
416 ){
417   static const sqlite3_pcache_methods testPcache = {
418     (void*)&testpcacheGlobal,
419     testpcacheInit,
420     testpcacheShutdown,
421     testpcacheCreate,
422     testpcacheCachesize,
423     testpcachePagecount,
424     testpcacheFetch,
425     testpcacheUnpin,
426     testpcacheRekey,
427     testpcacheTruncate,
428     testpcacheDestroy,
429   };
430   static sqlite3_pcache_methods defaultPcache;
431   static int isInstalled = 0;
432 
433   assert( testpcacheGlobal.nInstance==0 );
434   assert( testpcacheGlobal.pDummy==0 );
435   assert( discardChance<=100 );
436   testpcacheGlobal.discardChance = discardChance;
437   testpcacheGlobal.prngSeed = prngSeed ^ (prngSeed<<16);
438   testpcacheGlobal.highStress = highStress;
439   if( installFlag!=isInstalled ){
440     if( installFlag ){
441       sqlite3_config(SQLITE_CONFIG_GETPCACHE, &defaultPcache);
442       assert( defaultPcache.xCreate!=testpcacheCreate );
443       sqlite3_config(SQLITE_CONFIG_PCACHE, &testPcache);
444     }else{
445       assert( defaultPcache.xCreate!=0 );
446       sqlite3_config(SQLITE_CONFIG_PCACHE, &defaultPcache);
447     }
448     isInstalled = installFlag;
449   }
450 }
451