** file simultaneously, or one process from reading the database while
** another is writing.
**
-** @(#) $Id: pager.c,v 1.479 2008/08/25 17:23:29 drh Exp $
+** @(#) $Id: pager.c,v 1.480 2008/08/26 18:05:48 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"
*/
#define PAGER_MAX_PGNO 2147483647
-/*
-** The following two macros act as a type of recursive mutex. Their
-** only purpose is to provide mutual exclusion between the "normal"
-** users of a pager object (the btree.c module) and the user of the
-** pagerStress() function (the pcache.c module). While the mutex
-** obtained using pagerEnter() is held, the pcache module guarantees
-** that the pagerStress() callback will not be invoked from a thread
-** other than the holder of the mutex.
-*/
-#define pagerEnter(p) (sqlite3PcacheLock(p->pPCache))
-#define pagerLeave(p) (sqlite3PcacheUnlock(p->pPCache))
-
/*
** Return true if page *pPg has already been written to the statement
** journal (or statement snapshot has been created, if *pPg is part
int rc = SQLITE_OK;
u16 pageSize = *pPageSize;
assert( pageSize==0 || (pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE) );
- pagerEnter(pPager);
if( pageSize && pageSize!=pPager->pageSize
&& !pPager->memDb && sqlite3PcacheRefCount(pPager->pPCache)==0
){
}
}
*pPageSize = pPager->pageSize;
- pagerLeave(pPager);
return rc;
}
int rc = SQLITE_OK;
memset(pDest, 0, N);
assert(MEMDB||pPager->fd->pMethods||pPager->tempFile);
- pagerEnter(pPager);
if( pPager->fd->pMethods ){
IOTRACE(("DBHDR %p 0 %d\n", pPager, N))
rc = sqlite3OsRead(pPager->fd, pDest, N, 0);
rc = SQLITE_OK;
}
}
- pagerLeave(pPager);
return rc;
}
i64 n = 0;
int rc;
assert( pPager!=0 );
- pagerEnter(pPager);
if( pPager->errCode ){
rc = pPager->errCode;
- pagerLeave(pPager);
return rc;
}
if( pPager->dbSize>=0 ){
if( (pPager->fd->pMethods)
&& (rc = sqlite3OsFileSize(pPager->fd, &n))!=SQLITE_OK ){
pager_error(pPager, rc);
- pagerLeave(pPager);
return rc;
}
if( n>0 && n<pPager->pageSize ){
if( pnPage ){
*pnPage = n;
}
- pagerLeave(pPager);
return SQLITE_OK;
}
int rc = SQLITE_OK;
assert( pPager->state>=PAGER_SHARED || MEMDB );
- pagerEnter(pPager);
sqlite3PagerPagecount(pPager, 0);
if( pPager->errCode ){
}
}
- pagerLeave(pPager);
return rc;
}
** to the caller.
*/
int sqlite3PagerClose(Pager *pPager){
- pagerEnter(pPager);
disable_simulated_io_errors();
sqlite3BeginBenignMalloc();
** a reference to the page data.
*/
int sqlite3PagerRef(DbPage *pPg){
- pagerEnter(pPg->pPager);
sqlite3PcacheRef(pPg);
- pagerLeave(pPg->pPager);
return SQLITE_OK;
}
int noContent /* Do not bother reading content from disk if true */
){
int rc;
- pagerEnter(pPager);
rc = pagerAcquire(pPager, pgno, ppPage, noContent);
- pagerLeave(pPager);
return rc;
}
assert( pPager!=0 );
assert( pgno!=0 );
- pagerEnter(pPager);
if( (pPager->state!=PAGER_UNLOCK)
&& (pPager->errCode==SQLITE_OK || pPager->errCode==SQLITE_FULL)
){
sqlite3PcacheFetch(pPager->pPCache, pgno, 0, &pPg);
}
- pagerLeave(pPager);
return pPg;
}
int sqlite3PagerUnref(DbPage *pPg){
if( pPg ){
Pager *pPager = pPg->pPager;
- pagerEnter(pPager);
sqlite3PcacheRelease(pPg);
pagerUnlockIfUnused(pPager);
- pagerLeave(pPager);
}
return SQLITE_OK;
}
assert( pPager->useJournal );
assert( pPager->pInJournal==0 );
sqlite3PagerPagecount(pPager, 0);
- pagerLeave(pPager);
pPager->pInJournal = sqlite3BitvecCreate(pPager->dbSize);
- pagerEnter(pPager);
if( pPager->pInJournal==0 ){
rc = SQLITE_NOMEM;
goto failed_to_open_journal;
int sqlite3PagerBegin(DbPage *pPg, int exFlag){
Pager *pPager = pPg->pPager;
int rc = SQLITE_OK;
- pagerEnter(pPager);
assert( pPg->nRef>0 );
assert( pPager->state!=PAGER_UNLOCK );
if( pPager->state==PAGER_SHARED ){
}
}
if( rc!=SQLITE_OK ){
- pagerLeave(pPager);
return rc;
}
pPager->dirtyCache = 0;
}
}
assert( !pPager->journalOpen || pPager->journalOff>0 || rc!=SQLITE_OK );
- pagerLeave(pPager);
return rc;
}
Pager *pPager = pPg->pPager;
Pgno nPagePerSector = (pPager->sectorSize/pPager->pageSize);
- pagerEnter(pPager);
if( !MEMDB && nPagePerSector>1 ){
Pgno nPageCount; /* Total number of pages in database file */
Pgno pg1; /* First page of the sector pPg is located on. */
}else{
rc = pager_write(pDbPage);
}
- pagerLeave(pPager);
return rc;
}
Pager *pPager = pPg->pPager;
if( MEMDB ) return;
- pagerEnter(pPager);
pPg->flags |= PGHDR_ALWAYS_ROLLBACK;
if( (pPg->flags&PGHDR_DIRTY) && !pPager->stmtInUse ){
assert( pPager->state>=PAGER_SHARED );
#endif
}
}
- pagerLeave(pPager);
}
/*
void sqlite3PagerDontRollback(DbPage *pPg){
Pager *pPager = pPg->pPager;
- pagerEnter(pPager);
assert( pPager->state>=PAGER_RESERVED );
/* If the journal file is not open, or DontWrite() has been called on
if( pPager->journalOpen==0 || (pPg->flags&PGHDR_ALWAYS_ROLLBACK)
|| pPager->alwaysRollback
){
- pagerLeave(pPager);
return;
}
assert( !MEMDB ); /* For a memdb, pPager->journalOpen is always 0 */
#ifdef SQLITE_SECURE_DELETE
if( pPg->inJournal || (int)pPg->pgno > pPager->origDbSize ){
- pagerLeave(pPager);
return;
}
#endif
}
PAGERTRACE3("DONT_ROLLBACK page %d of %d\n", pPg->pgno, PAGERID(pPager));
IOTRACE(("GARBAGE %p %d\n", pPager, pPg->pgno))
- pagerLeave(pPager);
}
*/
int sqlite3PagerSync(Pager *pPager){
int rc;
- pagerEnter(pPager);
rc = sqlite3OsSync(pPager->fd, pPager->sync_flags);
- pagerLeave(pPager);
return rc;
}
PAGERTRACE4("DATABASE SYNC: File=%s zMaster=%s nTrunc=%d\n",
pPager->zFilename, zMaster, nTrunc);
- pagerEnter(pPager);
/* If this is an in-memory db, or no pages have been written to, or this
** function has already been called, it is a no-op.
*/
rc = SQLITE_BUSY;
}
- pagerLeave(pPager);
return rc;
}
assert( pPager->dirtyCache==0 || pPager->journalOpen==0 );
return SQLITE_OK;
}
- pagerEnter(pPager);
PAGERTRACE2("COMMIT %d\n", PAGERID(pPager));
if( MEMDB ){
sqlite3PcacheCommit(pPager->pPCache, 0);
rc = pager_end_transaction(pPager, pPager->setMaster);
rc = pager_error(pPager, rc);
}
- pagerLeave(pPager);
return rc;
}
int sqlite3PagerRollback(Pager *pPager){
int rc = SQLITE_OK;
PAGERTRACE2("ROLLBACK %d\n", PAGERID(pPager));
- pagerEnter(pPager);
if( MEMDB ){
sqlite3PcacheRollback(pPager->pPCache, 1);
sqlite3PcacheRollback(pPager->pPCache, 0);
*/
rc = pager_error(pPager, rc);
}
- pagerLeave(pPager);
return rc;
}
*/
int *sqlite3PagerStats(Pager *pPager){
static int a[11];
- pagerEnter(pPager);
a[0] = sqlite3PcacheRefCount(pPager->pPCache);
a[1] = sqlite3PcachePagecount(pPager->pPCache);
a[2] = sqlite3PcacheGetCachesize(pPager->pPCache);
a[8] = 0; /* Used to be pPager->nOvfl */
a[9] = pPager->nRead;
a[10] = pPager->nWrite;
- pagerLeave(pPager);
return a;
}
int sqlite3PagerIsMemdb(Pager *pPager){
}
int sqlite3PagerStmtBegin(Pager *pPager){
int rc;
- pagerEnter(pPager);
rc = pagerStmtBegin(pPager);
- pagerLeave(pPager);
return rc;
}
** Commit a statement.
*/
int sqlite3PagerStmtCommit(Pager *pPager){
- pagerEnter(pPager);
if( pPager->stmtInUse ){
PAGERTRACE2("STMT-COMMIT %d\n", PAGERID(pPager));
if( !MEMDB ){
pPager->stmtInUse = 0;
}
pPager->stmtAutoopen = 0;
- pagerLeave(pPager);
return SQLITE_OK;
}
*/
int sqlite3PagerStmtRollback(Pager *pPager){
int rc;
- pagerEnter(pPager);
if( pPager->stmtInUse ){
PAGERTRACE2("STMT-ROLLBACK %d\n", PAGERID(pPager));
if( MEMDB ){
rc = SQLITE_OK;
}
pPager->stmtAutoopen = 0;
- pagerLeave(pPager);
return rc;
}
PAGERID(pPager), pPg->pgno, (pPg->flags&PGHDR_NEED_SYNC)?1:0, pgno);
IOTRACE(("MOVE %p %d %d\n", pPager, pPg->pgno, pgno))
- pagerEnter(pPager);
-
pager_get_content(pPg);
/* If the journal needs to be sync()ed before page pPg->pgno can
if( pPager->pInJournal && (int)needSyncPgno<=pPager->origDbSize ){
sqlite3BitvecClear(pPager->pInJournal, needSyncPgno);
}
- pagerLeave(pPager);
return rc;
}
pPager->needSync = 1;
sqlite3PagerUnref(pPgHdr);
}
- pagerLeave(pPager);
return SQLITE_OK;
}
#endif
*************************************************************************
** This file implements that page cache.
**
-** @(#) $Id: pcache.c,v 1.13 2008/08/25 14:49:42 danielk1977 Exp $
+** @(#) $Id: pcache.c,v 1.14 2008/08/26 18:05:48 danielk1977 Exp $
*/
#include "sqliteInt.h"
** A complete page cache is an instance of this structure.
*/
struct PCache {
- PCache *pNextAll, *pPrevAll; /* List of all page caches */
int szPage; /* Size of every page in this cache */
int szExtra; /* Size of extra space for each page */
int nHash; /* Number of slots in apHash[] */
int nPage; /* Total number of pages in apHash */
int nMax; /* Configured cache size */
+ int nMin; /* Configured minimum cache size */
PgHdr **apHash; /* Hash table for fast lookup by pgno */
int bPurgeable; /* True if pages are on backing store */
void (*xDestroy)(PgHdr*); /* Called when refcnt goes 1->0 */
int (*xStress)(void*,PgHdr*); /* Call to try make a page clean */
void *pStress; /* Argument to xStress */
PgHdr *pClean; /* List of clean pages in use */
- PgHdr *pDirty; /* List of dirty pages */
- int nRef; /* Number of outstanding page refs */
-
- int iInUseMM;
- int iInUseDB;
+ PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */
+ PgHdr *pSynced; /* Last synced page in dirty page list */
+ int nRef; /* Number of pinned pages */
+ int nPinned; /* Number of pinned and/or dirty pages */
};
/*
** pages held by purgable caches to mxPagePurgeable.
**
** The doubly-linked list that runs between pcache.pLruHead and
-** pcache.pLruTail contains all pages in the system with a zero
-** reference count. The pcache.pLruSynced variable points to the last
-** (closest to pcache.pLruTail) entry in this list that does not have
-** the PGHDR_NEED_SYNC flag set. This is the page that the pcacheRecycle()
-** function will try to recycle.
+** pcache.pLruTail contains all clean purgable pages in the system
+** with a zero reference count. pcache.pLruTail is the next page to
+** be recycled.
*/
static struct PCacheGlobal {
int isInit; /* True when initialized */
- sqlite3_mutex *mutex_mem2; /* static mutex MUTEX_STATIC_MEM2 */
- sqlite3_mutex *mutex_lru; /* static mutex MUTEX_STATIC_LRU */
- PCache *pAll; /* list of all page caches */
- int nPage; /* Number of pages */
- int nPurgeable; /* Number of pages in purgable caches */
- int mxPage; /* Globally configured page maximum */
- int mxPagePurgeable; /* Purgeable page maximum */
- PgHdr *pLruHead, *pLruTail; /* Global LRU list of unused pages */
- PgHdr *pLruSynced; /* Last synced entry in LRU list */
+ sqlite3_mutex *mutex; /* static mutex MUTEX_STATIC_LRU */
+
+ int nMaxPage; /* Sum of nMaxPage for purgeable caches */
+ int nMinPage; /* Sum of nMinPage for purgeable caches */
+ int nCurrentPage; /* Number of purgeable pages allocated */
+ PgHdr *pLruHead, *pLruTail; /* LRU list of unused clean pgs */
/* Variables related to SQLITE_CONFIG_PAGECACHE settings. */
int szSlot; /* Size of each free slot */
/*
** All global variables used by this module (most of which are grouped
-** together in global structure "pcache" above) except the list of all
-** pager-caches starting with pcache.pAll, are protected by the static
+** together in global structure "pcache" above) are protected by the static
** SQLITE_MUTEX_STATIC_LRU mutex. A pointer to this mutex is stored in
-** variable "pcache.mutex_lru".
+** variable "pcache.mutex".
**
** Access to the contents of the individual PCache structures is not
** protected. It is the job of the caller to ensure that these structures
-** are accessed in a thread-safe manner. However, this module provides the
-** functions sqlite3PcacheLock() and sqlite3PcacheUnlock() that may be used
-** by the caller to increment/decrement a lock-count on an individual
-** pager-cache object. This module guarantees that the xStress() callback
-** will not be invoked on a pager-cache with a non-zero lock-count except
-** from within a call to sqlite3PcacheFetch() on the same pager. A call
-** to sqlite3PcacheLock() may block if such an xStress() call is currently
-** underway.
-**
-** Before the xStress callback of a pager-cache (PCache) is invoked, the
-** SQLITE_MUTEX_STATIC_MEM2 mutex is obtained.
-**
-** Deadlock within the module is avoided by never blocking on the MEM2
-** mutex while the LRU mutex is held.
+** are accessed in a thread-safe manner.
*/
-#define pcacheEnterGlobal() sqlite3_mutex_enter(pcache.mutex_lru)
-#define pcacheExitGlobal() sqlite3_mutex_leave(pcache.mutex_lru)
+#define pcacheEnterGlobal() sqlite3_mutex_enter(pcache.mutex)
+#define pcacheExitGlobal() sqlite3_mutex_leave(pcache.mutex)
/********************************** Linked List Management ********************/
assert( nPage==pCache->nPage );
return 1;
}
+
+/*
+** Based on the current value of PCache.nRef and the contents of the
+** PCache.pDirty list, return the expected value of the PCache.nPinned
+** counter. This is only used in debugging builds, as follows:
+**
+** assert( pCache->nPinned==pcachePinnedCount(pCache) );
+*/
+static int pcachePinnedCount(PCache *pCache){
+ PgHdr *p;
+ int nPinned = pCache->nRef;
+ for(p=pCache->pDirty; p; p=p->pNext){
+ if( p->nRef==0 ){
+ nPinned++;
+ }
+ }
+ return nPinned;
+}
+
+/*
+** Check that the pCache->pSynced variable is set correctly. If it
+** is not, either fail an assert or return zero. Otherwise, return
+** non-zero. This is only used in debugging builds, as follows:
+**
+** assert( pcacheCheckSynced(pCache) );
+*/
+static int pcacheCheckSynced(PCache *pCache){
+ PgHdr *p = pCache->pDirtyTail;
+ for(p=pCache->pDirtyTail; p!=pCache->pSynced; p=p->pPrev){
+ assert( p->nRef || (p->flags&PGHDR_NEED_SYNC) );
+ }
+ return (p==0 || p->nRef || (p->flags&PGHDR_NEED_SYNC)==0);
+}
+
#endif
/*
** *ppHead is either PCache.pClean or PCache.pDirty.
*/
static void pcacheRemoveFromList(PgHdr **ppHead, PgHdr *pPage){
+ int isDirtyList = (ppHead==&pPage->pCache->pDirty);
+ assert( ppHead==&pPage->pCache->pClean || ppHead==&pPage->pCache->pDirty );
+
if( pPage->pPrev ){
pPage->pPrev->pNext = pPage->pNext;
}else{
if( pPage->pNext ){
pPage->pNext->pPrev = pPage->pPrev;
}
+
+ if( isDirtyList ){
+ PCache *pCache = pPage->pCache;
+ assert( pPage->pNext || pCache->pDirtyTail==pPage );
+ if( !pPage->pNext ){
+ pCache->pDirtyTail = pPage->pPrev;
+ }
+ if( pCache->pSynced==pPage ){
+ PgHdr *pSynced = pPage->pPrev;
+ while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){
+ pSynced = pSynced->pPrev;
+ }
+ pCache->pSynced = pSynced;
+ }
+ }
}
/*
** *ppHead is either PCache.pClean or PCache.pDirty.
*/
static void pcacheAddToList(PgHdr **ppHead, PgHdr *pPage){
+ int isDirtyList = (ppHead==&pPage->pCache->pDirty);
+ assert( ppHead==&pPage->pCache->pClean || ppHead==&pPage->pCache->pDirty );
+
if( (*ppHead) ){
(*ppHead)->pPrev = pPage;
}
pPage->pNext = *ppHead;
pPage->pPrev = 0;
*ppHead = pPage;
+
+ if( isDirtyList ){
+ PCache *pCache = pPage->pCache;
+ if( !pCache->pDirtyTail ){
+ assert( pPage->pNext==0 );
+ pCache->pDirtyTail = pPage;
+ }
+ if( !pCache->pSynced && 0==(pPage->flags&PGHDR_NEED_SYNC) ){
+ pCache->pSynced = pPage;
+ }
+ }
}
/*
** Remove a page from the global LRU list
*/
static void pcacheRemoveFromLruList(PgHdr *pPage){
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
+ assert( sqlite3_mutex_held(pcache.mutex) );
+ assert( (pPage->flags&PGHDR_DIRTY)==0 );
if( pPage->pCache->bPurgeable==0 ) return;
- if( pPage==pcache.pLruSynced ){
- PgHdr *p;
- for(p=pPage->pPrevLru; p && (p->flags&PGHDR_NEED_SYNC); p=p->pPrevLru);
- pcache.pLruSynced = p;
- }
if( pPage->pNextLru ){
+ assert( pcache.pLruTail!=pPage );
pPage->pNextLru->pPrevLru = pPage->pPrevLru;
}else{
assert( pcache.pLruTail==pPage );
pcache.pLruTail = pPage->pPrevLru;
}
if( pPage->pPrevLru ){
+ assert( pcache.pLruHead!=pPage );
pPage->pPrevLru->pNextLru = pPage->pNextLru;
}else{
assert( pcache.pLruHead==pPage );
** to the end of the LRU list so that it will be the next to be recycled.
*/
static void pcacheAddToLruList(PgHdr *pPage){
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
+ assert( sqlite3_mutex_held(pcache.mutex) );
+ assert( (pPage->flags&PGHDR_DIRTY)==0 );
if( pPage->pCache->bPurgeable==0 ) return;
if( pcache.pLruTail && (pPage->flags & PGHDR_REUSE_UNLIKELY)!=0 ){
/* If reuse is unlikely. Put the page at the end of the LRU list
pcache.pLruTail->pNextLru = pPage;
pcache.pLruTail = pPage;
pPage->flags &= ~PGHDR_REUSE_UNLIKELY;
- if( 0==(pPage->flags&PGHDR_NEED_SYNC) ){
- pcache.pLruSynced = pPage;
- }
}else{
/* If reuse is possible. the page goes at the beginning of the LRU
** list so that it will be the last to be recycled.
if( pcache.pLruTail==0 ){
pcache.pLruTail = pPage;
}
- if( pcache.pLruSynced==0 && 0==(pPage->flags&PGHDR_NEED_SYNC) ){
- pcache.pLruSynced = pPage;
- }
}
}
** in the page cache memory pool, go to the general purpose memory allocator.
*/
void *pcacheMalloc(int sz, PCache *pCache){
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
+ assert( sqlite3_mutex_held(pcache.mutex) );
if( sz<=pcache.szSlot && pcache.pFree ){
PgFreeslot *p = pcache.pFree;
pcache.pFree = p->pNext;
** the global LRU mutex.
*/
pcacheExitGlobal();
- sqlite3PcacheUnlock(pCache);
p = sqlite3Malloc(sz);
- sqlite3PcacheLock(pCache);
pcacheEnterGlobal();
if( p ){
** Release a pager memory allocation
*/
void pcacheFree(void *p){
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
+ assert( sqlite3_mutex_held(pcache.mutex) );
if( p==0 ) return;
if( p>=pcache.pStart && p<pcache.pEnd ){
PgFreeslot *pSlot;
static PgHdr *pcachePageAlloc(PCache *pCache){
PgHdr *p;
int sz = sizeof(*p) + pCache->szPage + pCache->szExtra;
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
+ assert( sqlite3_mutex_held(pcache.mutex) );
p = pcacheMalloc(sz, pCache);
if( p==0 ) return 0;
memset(p, 0, sizeof(PgHdr));
p->pData = (void*)&p[1];
p->pExtra = (void*)&((char*)p->pData)[pCache->szPage];
- pcache.nPage++;
if( pCache->bPurgeable ){
- pcache.nPurgeable++;
+ pcache.nCurrentPage++;
}
-
return p;
}
** Deallocate a page
*/
static void pcachePageFree(PgHdr *p){
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
- pcache.nPage--;
+ assert( sqlite3_mutex_held(pcache.mutex) );
if( p->pCache->bPurgeable ){
- pcache.nPurgeable--;
+ pcache.nCurrentPage--;
}
pcacheFree(p->apSave[0]);
pcacheFree(p->apSave[1]);
** the argument is passed to pcachePageFree().
*/
static int pcachePageSize(PgHdr *p){
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
+ assert( sqlite3_mutex_held(pcache.mutex) );
assert( !pcache.pStart );
assert( p->apSave[0]==0 );
assert( p->apSave[1]==0 );
}
#endif
-static int pcacheRecyclePage(PgHdr *p, PCache *pCache){
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
- assert( sqlite3_mutex_held(pcache.mutex_mem2) );
-
- PCache *pC = p->pCache;
- assert( pC->iInUseMM==0 );
- pC->iInUseMM = 1;
- if( pC->xStress && (pC->iInUseDB==0 || pC==pCache) ){
- pcacheExitGlobal();
- pC->xStress(pC->pStress, p);
- pcacheEnterGlobal();
- }
- pC->iInUseMM = 0;
-
- return (p->flags&PGHDR_DIRTY);
-}
-
/*
-** Recycle a page from the global LRU list. If no page can be recycled,
-** return NULL. Otherwise, the pointer returned points to a PgHdr
-** object that has been removed from all lists and hash tables in
-** which is was referenced. The caller may reuse the allocation directly
-** or may pass it to pcachePageFree() to return the memory to the heap
-** (or pcache.pFree list).
-*/
-static PgHdr *pcacheRecycle(PCache *pCache){
+** Attempt to 'recycle' a page from the global LRU list. Only clean,
+** unreferenced pages from purgeable caches are eligible for recycling.
+**
+** This function removes page pcache.pLruTail from the global LRU list,
+** and from the hash-table and PCache.pClean list of the owner pcache.
+** There should be no other references to the page.
+**
+** A pointer to the recycled page is returned, or NULL if no page is
+** eligible for recycling.
+*/
+static PgHdr *pcacheRecyclePage(){
PgHdr *p = 0;
+ assert( sqlite3_mutex_held(pcache.mutex) );
- assert( pcache.isInit );
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
-
- if( SQLITE_OK==sqlite3_mutex_try(pcache.mutex_mem2) ){
- p = pcache.pLruSynced;
- while( p && (p->flags&PGHDR_DIRTY) && pcacheRecyclePage(p, pCache) ){
- do { p = p->pPrevLru; } while( p && (p->flags&PGHDR_NEED_SYNC) );
- }
- if( !p ){
- p = pcache.pLruTail;
- while( p && (p->flags&PGHDR_DIRTY) && pcacheRecyclePage(p, pCache) ){
- do { p = p->pPrevLru; } while( p && 0==(p->flags&PGHDR_NEED_SYNC) );
- }
- }
- sqlite3_mutex_leave(pcache.mutex_mem2);
- }
-
- if( p ){
+ if( (p=pcache.pLruTail) ){
+ assert( (p->flags&PGHDR_DIRTY)==0 );
pcacheRemoveFromLruList(p);
pcacheRemoveFromHash(p);
pcacheRemoveFromList(&p->pCache->pClean, p);
/* If the always-rollback flag is set on the page being recycled, set
- ** the always-rollback flag on the corresponding pager.
+ ** the always-rollback flag on the corresponding pager. TODO: This is
+ ** a thread-safety problem.
*/
if( p->flags&PGHDR_ALWAYS_ROLLBACK ){
assert(p->pPager);
int szPage = pCache->szPage;
int szExtra = pCache->szExtra;
- int bPurg = pCache->bPurgeable;
assert( pcache.isInit );
- assert( sqlite3_mutex_notheld(pcache.mutex_lru) );
+ assert( sqlite3_mutex_notheld(pcache.mutex) );
pcacheEnterGlobal();
- if( (pcache.mxPage && pcache.nPage>=pcache.mxPage)
- || (!pcache.mxPage && bPurg && pcache.nPurgeable>=pcache.mxPagePurgeable)
+ /* 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.
+ */
+ assert( pCache->nPinned==pcachePinnedCount(pCache) );
+ assert( pcacheCheckSynced(pCache) );
+ if( pCache->xStress
+ && pCache->pDirty
+ && pCache->nPinned>=(pcache.nMaxPage+pCache->nMin-pcache.nMinPage)
){
- /* If the above test succeeds, then try to obtain a buffer by recycling
- ** an existing page. */
- p = pcacheRecycle(pCache);
+ 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 ){
+ pcacheExitGlobal();
+ pCache->xStress(pCache->pStress, pPg);
+ pcacheEnterGlobal();
+ }
+ }
+
+ /* If the global page limit has been reached, try to recycle a page. */
+ if( 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->szExtra!=szExtra) ){
pcachePageFree(p);
p = 0;
assert( pcache.isInit==0 );
memset(&pcache, 0, sizeof(pcache));
if( sqlite3Config.bCoreMutex ){
- pcache.mutex_lru = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU);
- pcache.mutex_mem2 = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM2);
- if( pcache.mutex_lru==0 || pcache.mutex_mem2==0 ){
+ pcache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU);
+ if( pcache.mutex==0 ){
return SQLITE_NOMEM;
}
}
p->xStress = xStress;
p->pStress = pStress;
p->nMax = 100;
+ p->nMin = 20;
+ pcacheEnterGlobal();
if( bPurgeable ){
- pcacheEnterGlobal();
- pcache.mxPagePurgeable += p->nMax;
- pcacheExitGlobal();
+ pcache.nMaxPage += p->nMax;
+ pcache.nMinPage += p->nMin;
}
- /* Add the new pager-cache to the list of caches starting at pcache.pAll */
- pcacheEnterGlobal();
- p->pNextAll = pcache.pAll;
- if( pcache.pAll ){
- pcache.pAll->pPrevAll = p;
- }
- p->pPrevAll = 0;
- pcache.pAll = p;
pcacheExitGlobal();
}
assert( pcache.isInit );
assert( pCache!=0 );
assert( pgno>0 );
- assert( pCache->iInUseDB || pCache->iInUseMM );
+ assert( pCache->nPinned==pcachePinnedCount(pCache) );
/* 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 /* && (pPage->flags & PGHDR_DIRTY)==0 */ ){
- pcacheEnterGlobal();
- pcacheRemoveFromLruList(pPage);
- pcacheExitGlobal();
- }
- if( (pPage->nRef++)==0 ){
+ if( pPage->nRef==0 ){
+ if( 0==(pPage->flags&PGHDR_DIRTY) ){
+ pcacheEnterGlobal();
+ pcacheRemoveFromLruList(pPage);
+ pcacheExitGlobal();
+ pCache->nPinned++;
+ }
pCache->nRef++;
}
+ pPage->nRef++;
*ppPage = pPage;
return SQLITE_OK;
}
pPage->pCache = pCache;
pPage->nRef = 1;
pCache->nRef++;
+ pCache->nPinned++;
pcacheAddToList(&pCache->pClean, pPage);
pcacheAddToHash(pPage);
}else{
*ppPage = 0;
}
+ assert( pCache->nPinned==pcachePinnedCount(pCache) );
return SQLITE_OK;
}
*/
void sqlite3PcacheRelease(PgHdr *p){
assert( p->nRef>0 );
- assert( p->pCache->iInUseDB || p->pCache->iInUseMM );
p->nRef--;
if( p->nRef==0 ){
PCache *pCache = p->pCache;
- pCache->nRef--;
if( p->pCache->xDestroy ){
p->pCache->xDestroy(p);
}
-#if 0
- if( (p->flags & PGHDR_DIRTY)!=0 ) return;
-#endif
- pcacheEnterGlobal();
- pcacheAddToLruList(p);
- pcacheExitGlobal();
+ pCache->nRef--;
+ if( (p->flags&PGHDR_DIRTY)==0 ){
+ pCache->nPinned--;
+ pcacheEnterGlobal();
+ pcacheAddToLruList(p);
+ pcacheExitGlobal();
+ }else{
+ /* Move the page to the head of the caches dirty list. */
+ pcacheRemoveFromList(&pCache->pDirty, p);
+ pcacheAddToList(&pCache->pDirty, p);
+ }
}
}
*/
void sqlite3PcacheDrop(PgHdr *p){
PCache *pCache;
- assert( p->pCache->iInUseDB );
assert( p->nRef==1 );
pCache = p->pCache;
pCache->nRef--;
+ pCache->nPinned--;
if( p->flags & PGHDR_DIRTY ){
pcacheRemoveFromList(&pCache->pDirty, p);
}else{
*/
void sqlite3PcacheMakeDirty(PgHdr *p){
PCache *pCache;
- assert( p->pCache->iInUseDB );
p->flags &= ~PGHDR_DONT_WRITE;
if( p->flags & PGHDR_DIRTY ) return;
assert( (p->flags & PGHDR_DIRTY)==0 );
*/
void sqlite3PcacheMakeClean(PgHdr *p){
PCache *pCache;
- assert( p->pCache->iInUseDB || p->pCache->iInUseMM );
if( (p->flags & PGHDR_DIRTY)==0 ) return;
assert( p->apSave[0]==0 && p->apSave[1]==0 );
assert( p->flags & PGHDR_DIRTY );
pCache = p->pCache;
pcacheRemoveFromList(&pCache->pDirty, p);
+ pcacheEnterGlobal();
pcacheAddToList(&pCache->pClean, p);
p->flags &= ~PGHDR_DIRTY;
+ if( p->nRef==0 ){
+ pcacheAddToLruList(p);
+ pCache->nPinned--;
+ }
+ assert( pCache->nPinned==pcachePinnedCount(pCache) );
+ pcacheExitGlobal();
}
/*
*/
void sqlite3PcacheCleanAll(PCache *pCache){
PgHdr *p;
- assert( pCache->iInUseDB );
pcacheEnterGlobal();
while( (p = pCache->pDirty)!=0 ){
assert( p->apSave[0]==0 && p->apSave[1]==0 );
pcacheRemoveFromList(&pCache->pDirty, p);
- pcacheAddToList(&pCache->pClean, p);
p->flags &= ~PGHDR_DIRTY;
+ pcacheAddToList(&pCache->pClean, p);
+ if( p->nRef==0 ){
+ pcacheAddToLruList(p);
+ pCache->nPinned--;
+ }
}
sqlite3PcacheAssertFlags(pCache, 0, PGHDR_DIRTY);
+ assert( pCache->nPinned==pcachePinnedCount(pCache) );
pcacheExitGlobal();
}
** flag set.
*/
void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){
- assert( p->pCache->iInUseDB );
+ assert( p->nRef>0 );
pcacheRemoveFromHash(p);
p->pgno = newPgno;
if( newPgno==0 ){
pcacheAddToHash(p);
}
-/*
-** Set the global maximum number of pages. Return the previous value.
-*/
-void sqlite3PcacheGlobalMax(int mx){
- pcacheEnterGlobal();
- pcache.mxPage = mx;
- pcacheExitGlobal();
-}
-
/*
** Remove all content from a page cache
*/
void pcacheClear(PCache *pCache){
PgHdr *p, *pNext;
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
+ assert( sqlite3_mutex_held(pcache.mutex) );
for(p=pCache->pClean; p; p=pNext){
pNext = p->pNext;
pcacheRemoveFromLruList(p);
}
for(p=pCache->pDirty; p; p=pNext){
pNext = p->pNext;
- pcacheRemoveFromLruList(p);
pcachePageFree(p);
}
pCache->pClean = 0;
pCache->pDirty = 0;
+ pCache->pDirtyTail = 0;
pCache->nPage = 0;
+ pCache->nPinned = 0;
memset(pCache->apHash, 0, pCache->nHash*sizeof(pCache->apHash[0]));
}
void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
PgHdr *p, *pNext;
PgHdr *pDirty = pCache->pDirty;
- assert( pCache->iInUseDB );
pcacheEnterGlobal();
for(p=pCache->pClean; p||pDirty; p=pNext){
if( !p ){
pcacheRemoveFromHash(p);
if( p->flags&PGHDR_DIRTY ){
pcacheRemoveFromList(&pCache->pDirty, p);
+ pCache->nPinned--;
}else{
pcacheRemoveFromList(&pCache->pClean, p);
+ pcacheRemoveFromLruList(p);
}
- pcacheRemoveFromLruList(p);
pcachePageFree(p);
}else{
/* If there are references to the page, it cannot be freed. In this
** Close a cache.
*/
void sqlite3PcacheClose(PCache *pCache){
- assert( pCache->iInUseDB==1 );
pcacheEnterGlobal();
/* Free all the pages used by this pager and remove them from the LRU list. */
pcacheClear(pCache);
if( pCache->bPurgeable ){
- pcache.mxPagePurgeable -= pCache->nMax;
+ pcache.nMaxPage -= pCache->nMax;
+ pcache.nMinPage -= pCache->nMin;
}
sqlite3_free(pCache->apHash);
- /* Now remove the pager-cache structure itself from the list of
- ** all such structures headed by pcache.pAll.
- */
- assert(pCache==pcache.pAll || pCache->pPrevAll);
- assert(pCache->pNextAll==0 || pCache->pNextAll->pPrevAll==pCache);
- assert(pCache->pPrevAll==0 || pCache->pPrevAll->pNextAll==pCache);
- if( pCache->pPrevAll ){
- pCache->pPrevAll->pNextAll = pCache->pNextAll;
- }else{
- pcache.pAll = pCache->pNextAll;
- }
- if( pCache->pNextAll ){
- pCache->pNextAll->pPrevAll = pCache->pPrevAll;
- }
-
pcacheExitGlobal();
}
int sqlite3PcachePreserve(PgHdr *p, int idJournal){
void *x;
int sz;
- assert( p->pCache->iInUseDB );
assert( p->pCache->bPurgeable==0 );
if( !p->apSave[idJournal] ){
sz = p->pCache->szPage;
*/
void sqlite3PcacheCommit(PCache *pCache, int idJournal){
PgHdr *p;
- assert( pCache->iInUseDB );
pcacheEnterGlobal(); /* Mutex is required to call pcacheFree() */
for(p=pCache->pDirty; p; p=p->pNext){
if( p->apSave[idJournal] ){
void sqlite3PcacheRollback(PCache *pCache, int idJournal){
PgHdr *p;
int sz;
- assert( pCache->iInUseDB );
pcacheEnterGlobal(); /* Mutex is required to call pcacheFree() */
sz = pCache->szPage;
for(p=pCache->pDirty; p; p=p->pNext){
*/
void sqlite3PcacheAssertFlags(PCache *pCache, int trueMask, int falseMask){
PgHdr *p;
- assert( pCache->iInUseDB || pCache->iInUseMM );
for(p=pCache->pDirty; p; p=p->pNext){
assert( (p->flags&trueMask)==trueMask );
assert( (p->flags&falseMask)==0 );
** Discard the contents of the cache.
*/
int sqlite3PcacheClear(PCache *pCache){
- assert( pCache->iInUseDB );
assert(pCache->nRef==0);
pcacheEnterGlobal();
pcacheClear(pCache);
*/
PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
PgHdr *p;
- assert( pCache->iInUseDB );
for(p=pCache->pDirty; p; p=p->pNext){
p->pDirty = p->pNext;
}
** Return the total number of pages in the cache.
*/
int sqlite3PcachePagecount(PCache *pCache){
- assert( pCache->iInUseDB || pCache->iInUseMM );
assert( pCache->nPage>=0 );
return pCache->nPage;
}
*/
void sqlite3PcacheIterate(PCache *pCache, void (*xIter)(PgHdr *)){
PgHdr *p;
- assert( pCache->iInUseDB || pCache->iInUseMM );
for(p=pCache->pClean; p; p=p->pNext){
xIter(p);
}
PgHdr *p;
assert( (orMask&PGHDR_NEED_SYNC)==0 );
- assert( pCache->iInUseDB || pCache->iInUseMM );
/* Obtain the global mutex before modifying any PgHdr.flags variables
** or traversing the LRU list.
*/
pcacheEnterGlobal();
- assert( sqlite3_mutex_held(pcache.mutex_lru) );
for(p=pCache->pDirty; p; p=p->pNext){
p->flags = (p->flags&andMask)|orMask;
}
if( 0==(andMask&PGHDR_NEED_SYNC) ){
- for(p=pcache.pLruTail; p && (p->flags&PGHDR_NEED_SYNC); p=p->pPrevLru);
- pcache.pLruSynced = p;
+ PgHdr *pSynced = pCache->pDirtyTail;
+ while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){
+ pSynced = pSynced->pPrev;
+ }
+ pCache->pSynced = pSynced;
}
pcacheExitGlobal();
}
if( pCache->bPurgeable ){
pcacheEnterGlobal();
- pcache.mxPagePurgeable -= pCache->nMax;
- pcache.mxPagePurgeable += mxPage;
+ pcache.nMaxPage -= pCache->nMax;
+ pcache.nMaxPage += mxPage;
pcacheExitGlobal();
}
pCache->nMax = mxPage;
}
-/*
-** Lock a pager-cache.
-*/
-void sqlite3PcacheLock(PCache *pCache){
- if( pCache ){
- assert( sqlite3_mutex_notheld(pcache.mutex_lru) );
- pCache->iInUseDB++;
- if( pCache->iInUseMM && pCache->iInUseDB==1 ){
- pCache->iInUseDB = 0;
- sqlite3_mutex_enter(pcache.mutex_mem2);
- assert( pCache->iInUseMM==0 && pCache->iInUseDB==0 );
- pCache->iInUseDB = 1;
- sqlite3_mutex_leave(pcache.mutex_mem2);
- }
- }
-}
-
-/*
-** Unlock a pager-cache.
-*/
-void sqlite3PcacheUnlock(PCache *pCache){
- if( pCache ){
- pCache->iInUseDB--;
- assert( pCache->iInUseDB>=0 );
- }
-}
-
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
/*
** This function is called to free superfluous dynamically allocated memory
if( pcache.pStart==0 ){
PgHdr *p;
pcacheEnterGlobal();
- while( (nReq<0 || nFree<nReq) && (p=pcacheRecycle(0)) ){
+ while( (nReq<0 || nFree<nReq) && (p=pcacheRecyclePage()) ){
nFree += pcachePageSize(p);
pcachePageFree(p);
}