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 |