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