OLD | NEW |
| (Empty) |
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 } | |
OLD | NEW |