blob: 58f5b398165d71c4abda52956a5389af4e85d6bc [file] [log] [blame]
drhb232c232008-11-19 01:20:26 +00001/*
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*/
35typedef struct testpcacheGlobalType testpcacheGlobalType;
36struct 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};
42static 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*/
54static 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*/
68static 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*/
91typedef struct testpcache testpcache;
92struct 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*/
109static 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*/
123static 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*/
152static 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*/
164static 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*/
175static 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*/
263static 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*/
308static 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*/
356static 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*/
378static 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*/
401void 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}