/* ** Obtain space for a page. Try to recycle an old page if the limit on the ** number of pages has been reached. If the limit has not been reached or ** there are no pages eligible for recycling, allocate a new page. ** ** Return a pointer to the new page, or NULL if an OOM condition occurs. */ static int pcacheRecycleOrAlloc(PCache *pCache, PgHdr **ppPage) { PgHdr *p = 0; int szPage = pCache->szPage; int szExtra = pCache->szExtra; assert( pcache.isInit ); assert( sqlite3_mutex_held(pcache.mutex) ); *ppPage = 0; /* If we have reached the limit for pinned/dirty pages, and there is at ** least one dirty page, invoke the xStress callback to cause a page to ** become clean. */ expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); expensive_assert( pcacheCheckSynced(pCache) ); if( pCache->xStress && pCache->pDirty && pCache->nPinned>=(pcache.nMaxPage+pCache->nMin-pcache.nMinPage) ) { PgHdr *pPg; assert(pCache->pDirtyTail); for(pPg=pCache->pSynced; pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); pPg=pPg->pPrev ); if( !pPg ) { for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pPrev); } if( pPg ) { int rc; pcacheExitMutex(); rc = pCache->xStress(pCache->pStress, pPg); pcacheEnterMutex(); if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ) { return rc; } } } /* If the global page limit has been reached, try to recycle a page. */ if( pCache->bPurgeable && pcache.nCurrentPage>=pcache.nMaxPage ) { p = pcacheRecyclePage(); } /* If a page has been recycled but it is the wrong size, free it. */ if( p && (p->pCache->szPage!=szPage || p->pCache->szPage!=szExtra) ) { pcachePageFree(p); p = 0; } if( !p ) { p = pcachePageAlloc(pCache); } *ppPage = p; return (p?SQLITE_OK:SQLITE_NOMEM); }
static void pcacheMakeClean(PgHdr *p){ PCache *pCache = p->pCache; assert( p->flags & PGHDR_DIRTY ); pcacheRemoveFromList(&pCache->pDirty, p); pcacheAddToList(&pCache->pClean, p); p->flags &= ~PGHDR_DIRTY; if( p->nRef==0 ){ pcacheAddToLruList(p); pCache->nPinned--; } expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); }
/* ** Make every page in the cache clean. */ void sqlite3PcacheCleanAll(PCache *pCache){ PgHdr *p; pcacheEnterMutex(); while( (p = pCache->pDirty)!=0 ){ pcacheRemoveFromList(&pCache->pDirty, p); p->flags &= ~PGHDR_DIRTY; pcacheAddToList(&pCache->pClean, p); if( p->nRef==0 ){ pcacheAddToLruList(p); pCache->nPinned--; } } sqlite3PcacheAssertFlags(pCache, 0, PGHDR_DIRTY); expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); pcacheExitMutex(); }
/* ** Try to obtain a page from the cache. */ int sqlite3PcacheFetch( PCache *pCache, /* Obtain the page from this cache */ Pgno pgno, /* Page number to obtain */ int createFlag, /* If true, create page if it does not exist already */ PgHdr **ppPage /* Write the page here */ ){ int rc = SQLITE_OK; PgHdr *pPage = 0; assert( pcache_g.isInit ); assert( pCache!=0 ); assert( pgno>0 ); expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); pcacheEnterMutex(); /* Search the hash table for the requested page. Exit early if it is found. */ if( pCache->apHash ){ u32 h = pgno % pCache->nHash; for(pPage=pCache->apHash[h]; pPage; pPage=pPage->pNextHash){ if( pPage->pgno==pgno ){ if( pPage->nRef==0 ){ if( 0==(pPage->flags&PGHDR_DIRTY) ){ pcacheRemoveFromLruList(pPage); pCache->nPinned++; } pCache->nRef++; } pPage->nRef++; break; } } } if( !pPage && createFlag ){ if( pCache->nHash<=pCache->nPage ){ rc = pcacheResizeHash(pCache, pCache->nHash<256 ? 256 : pCache->nHash*2); } if( rc==SQLITE_OK ){ rc = pcacheRecycleOrAlloc(pCache, &pPage); } if( rc==SQLITE_OK ){ pPage->pPager = 0; pPage->flags = 0; pPage->pDirty = 0; pPage->pgno = pgno; pPage->pCache = pCache; pPage->nRef = 1; pCache->nRef++; pCache->nPinned++; pcacheAddToList(&pCache->pClean, pPage); pcacheAddToHash(pPage); } } pcacheExitMutex(); *ppPage = pPage; expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); assert( pPage || !createFlag || rc!=SQLITE_OK ); return rc; }