Add an API to support custom page cache implementations. (CVS 5899)
FossilOrigin-Name: 47866d6708e9b69e367937fd85f93580fd025447
diff --git a/src/btree.c b/src/btree.c
index 9d497c5..18add6c 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -9,7 +9,7 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
-** $Id: btree.c,v 1.534 2008/11/12 18:21:36 danielk1977 Exp $
+** $Id: btree.c,v 1.535 2008/11/13 14:28:29 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
@@ -2603,9 +2603,14 @@
BtCursor *p;
sqlite3BtreeEnter(pBtree);
for(p=pBtree->pBt->pCursor; p; p=p->pNext){
+ int i;
sqlite3BtreeClearCursor(p);
p->eState = CURSOR_FAULT;
p->skip = errCode;
+ for(i=0; i<=p->iPage; i++){
+ releasePage(p->apPage[i]);
+ p->apPage[i] = 0;
+ }
}
sqlite3BtreeLeave(pBtree);
}
@@ -5615,7 +5620,8 @@
cdata = pChild->aData;
memcpy(cdata, &data[hdr], pPage->cellOffset+2*pPage->nCell-hdr);
memcpy(&cdata[cbrk], &data[cbrk], usableSize-cbrk);
-
+
+ assert( pChild->isInit==0 );
rc = sqlite3BtreeInitPage(pChild);
if( rc==SQLITE_OK ){
int nCopy = pPage->nOverflow*sizeof(pPage->aOvfl[0]);
diff --git a/src/main.c b/src/main.c
index 810da80..3662697 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,7 @@
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
-** $Id: main.c,v 1.511 2008/11/10 18:05:36 shane Exp $
+** $Id: main.c,v 1.512 2008/11/13 14:28:29 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@@ -309,6 +309,12 @@
break;
}
+ case SQLITE_CONFIG_PCACHE: {
+ /* Specify an alternative malloc implementation */
+ sqlite3GlobalConfig.pcache = *va_arg(ap, sqlite3_pcache_methods*);
+ break;
+ }
+
#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
case SQLITE_CONFIG_HEAP: {
/* Designate a buffer for heap memory space */
diff --git a/src/pager.c b/src/pager.c
index 4d24a36..b17be5f 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -18,7 +18,7 @@
** file simultaneously, or one process from reading the database while
** another is writing.
**
-** @(#) $Id: pager.c,v 1.502 2008/11/07 00:24:54 drh Exp $
+** @(#) $Id: pager.c,v 1.503 2008/11/13 14:28:29 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"
@@ -312,6 +312,10 @@
return sqlite3BitvecTest(pPager->pInStmt, pPg->pgno);
}
+static int pageInJournal(PgHdr *pPg){
+ return sqlite3BitvecTest(pPg->pPager->pInJournal, pPg->pgno);
+}
+
/*
** Read a 32-bit integer from the given file descriptor. Store the integer
** that is read in *pRes. Return SQLITE_OK if everything worked, or an
@@ -454,7 +458,7 @@
static u32 pager_pagehash(PgHdr *pPage){
return pager_datahash(pPage->pPager->pageSize, (unsigned char *)pPage->pData);
}
-static u32 pager_set_pagehash(PgHdr *pPage){
+static void pager_set_pagehash(PgHdr *pPage){
pPage->pageHash = pager_pagehash(pPage);
}
@@ -997,13 +1001,10 @@
pPager->pInJournal = 0;
sqlite3BitvecDestroy(pPager->pAlwaysRollback);
pPager->pAlwaysRollback = 0;
- sqlite3PcacheCleanAll(pPager->pPCache);
#ifdef SQLITE_CHECK_PAGES
- sqlite3PcacheIterate(pPager->pPCache, pager_set_pagehash);
+ sqlite3PcacheIterateDirty(pPager->pPCache, pager_set_pagehash);
#endif
- sqlite3PcacheClearFlags(pPager->pPCache,
- PGHDR_IN_JOURNAL | PGHDR_NEED_SYNC
- );
+ sqlite3PcacheCleanAll(pPager->pPCache);
pPager->dirtyCache = 0;
pPager->nRec = 0;
}else{
@@ -2339,19 +2340,9 @@
/* Erase the needSync flag from every page.
*/
- sqlite3PcacheClearFlags(pPager->pPCache, PGHDR_NEED_SYNC);
+ sqlite3PcacheClearSyncFlags(pPager->pPCache);
}
-#ifndef NDEBUG
- /* If the Pager.needSync flag is clear then the PgHdr.needSync
- ** flag must also be clear for all pages. Verify that this
- ** invariant is true.
- */
- else{
- sqlite3PcacheAssertFlags(pPager->pPCache, 0, PGHDR_NEED_SYNC);
- }
-#endif
-
return rc;
}
@@ -2852,9 +2843,6 @@
int nMax;
PAGER_INCR(pPager->nMiss);
pPg->pPager = pPager;
- if( sqlite3BitvecTest(pPager->pInJournal, pgno) ){
- pPg->flags |= PGHDR_IN_JOURNAL;
- }
memset(pPg->pExtra, 0, pPager->nExtra);
rc = sqlite3PagerPagecount(pPager, &nMax);
@@ -3058,7 +3046,6 @@
if( pPager->state==PAGER_SHARED ){
assert( pPager->pInJournal==0 );
assert( !MEMDB );
- sqlite3PcacheAssertFlags(pPager->pPCache, 0, PGHDR_IN_JOURNAL);
rc = sqlite3OsLock(pPager->fd, RESERVED_LOCK);
if( rc==SQLITE_OK ){
pPager->state = PAGER_RESERVED;
@@ -3167,9 +3154,7 @@
** to the journal then we can return right away.
*/
makeDirty(pPg);
- if( (pPg->flags&PGHDR_IN_JOURNAL)
- && (pageInStatement(pPg) || pPager->stmtInUse==0)
- ){
+ if( pageInJournal(pPg) && (pageInStatement(pPg) || pPager->stmtInUse==0) ){
pPager->dirtyCache = 1;
pPager->dbModified = 1;
}else{
@@ -3199,7 +3184,7 @@
** EXCLUSIVE lock on the main database file. Write the current page to
** the transaction journal if it is not there already.
*/
- if( !(pPg->flags&PGHDR_IN_JOURNAL) && pPager->journalOpen ){
+ if( !pageInJournal(pPg) && pPager->journalOpen ){
if( (int)pPg->pgno <= pPager->origDbSize ){
u32 cksum;
char *pData2;
@@ -3254,7 +3239,6 @@
if( pPg->flags&PGHDR_NEED_SYNC ){
pPager->needSync = 1;
}
- pPg->flags |= PGHDR_IN_JOURNAL;
}
/* If the statement journal is open and the page is not in it,
@@ -3268,8 +3252,7 @@
){
i64 offset = pPager->stmtNRec*(4+pPager->pageSize);
char *pData2 = CODEC2(pPager, pData, pPg->pgno, 7);
- assert( (pPg->flags&PGHDR_IN_JOURNAL)
- || (int)pPg->pgno>pPager->origDbSize );
+ assert( pageInJournal(pPg) || (int)pPg->pgno>pPager->origDbSize );
rc = write32bits(pPager->stfd, offset, pPg->pgno);
if( rc==SQLITE_OK ){
rc = sqlite3OsWrite(pPager->stfd, pData2, pPager->pageSize, offset+4);
@@ -3515,7 +3498,6 @@
assert( pPager->pInJournal!=0 );
sqlite3BitvecSet(pPager->pInJournal, pPg->pgno);
- pPg->flags |= PGHDR_IN_JOURNAL;
pPg->flags &= ~PGHDR_NEED_READ;
if( pPager->stmtInUse ){
assert( pPager->stmtSize >= pPager->origDbSize );
@@ -4077,7 +4059,7 @@
*/
if( (pPg->flags&PGHDR_NEED_SYNC) && !isCommit ){
needSyncPgno = pPg->pgno;
- assert( (pPg->flags&PGHDR_IN_JOURNAL) || (int)pgno>pPager->origDbSize );
+ assert( pageInJournal(pPg) || (int)pgno>pPager->origDbSize );
assert( pPg->flags&PGHDR_DIRTY );
assert( pPager->needSync );
}
@@ -4087,20 +4069,16 @@
** page pgno before the 'move' operation, it needs to be retained
** for the page moved there.
*/
- pPg->flags &= ~(PGHDR_NEED_SYNC|PGHDR_IN_JOURNAL);
+ pPg->flags &= ~PGHDR_NEED_SYNC;
pPgOld = pager_lookup(pPager, pgno);
assert( !pPgOld || pPgOld->nRef==1 );
if( pPgOld ){
pPg->flags |= (pPgOld->flags&PGHDR_NEED_SYNC);
}
- if( sqlite3BitvecTest(pPager->pInJournal, pgno) ){
- pPg->flags |= PGHDR_IN_JOURNAL;
- }
sqlite3PcacheMove(pPg, pgno);
if( pPgOld ){
- sqlite3PcacheMove(pPgOld, 0);
- sqlite3PcacheRelease(pPgOld);
+ sqlite3PcacheDrop(pPgOld);
}
makeDirty(pPg);
@@ -4138,7 +4116,6 @@
pPager->needSync = 1;
assert( pPager->noSync==0 && !MEMDB );
pPgHdr->flags |= PGHDR_NEED_SYNC;
- pPgHdr->flags |= PGHDR_IN_JOURNAL;
makeDirty(pPgHdr);
sqlite3PagerUnref(pPgHdr);
}
diff --git a/src/pcache.c b/src/pcache.c
index f313a72..6936b13 100644
--- a/src/pcache.c
+++ b/src/pcache.c
@@ -11,105 +11,29 @@
*************************************************************************
** This file implements that page cache.
**
-** @(#) $Id: pcache.c,v 1.36 2008/11/11 18:43:00 danielk1977 Exp $
+** @(#) $Id: pcache.c,v 1.37 2008/11/13 14:28:29 danielk1977 Exp $
*/
#include "sqliteInt.h"
/*
** A complete page cache is an instance of this structure.
-**
-** A cache may only be deleted by its owner and while holding the
-** SQLITE_MUTEX_STATUS_LRU mutex.
*/
struct PCache {
- /*********************************************************************
- ** The first group of elements may be read or written at any time by
- ** the cache owner without holding the mutex. No thread other than the
- ** cache owner is permitted to access these elements at any time.
- */
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 */
+ int nRef; /* Number of referenced pages */
int nMax; /* Configured cache size */
int nMin; /* Configured minimum cache size */
- /**********************************************************************
- ** The next group of elements are fixed when the cache is created and
- ** may not be changed afterwards. These elements can read at any time by
- ** the cache owner or by any thread holding the the mutex. Non-owner
- ** threads must hold the mutex when reading these elements to prevent
- ** the entire PCache object from being deleted during the read.
- */
int szPage; /* Size of every page in this cache */
int szExtra; /* Size of extra space for each page */
int bPurgeable; /* True if pages are on backing store */
int (*xStress)(void*,PgHdr*); /* Call to try make a page clean */
void *pStress; /* Argument to xStress */
- /**********************************************************************
- ** The final group of elements can only be accessed while holding the
- ** mutex. Both the cache owner and any other thread must hold the mutex
- ** to read or write any of these elements.
- */
- int nPage; /* Total number of pages in apHash */
- int nHash; /* Number of slots in apHash[] */
- PgHdr **apHash; /* Hash table for fast lookup by pgno */
- PgHdr *pClean; /* List of clean pages in use */
+ sqlite3_pcache *pCache; /* Pluggable cache module */
+ PgHdr *pPage1;
};
/*
-** Free slots in the page block allocator
-*/
-typedef struct PgFreeslot PgFreeslot;
-struct PgFreeslot {
- PgFreeslot *pNext; /* Next free slot */
-};
-
-/*
-** Global data for the page cache.
-*/
-static SQLITE_WSD struct PCacheGlobal {
- int isInit; /* True when initialized */
- 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 */
- void *pStart, *pEnd; /* Bounds of pagecache malloc range */
- PgFreeslot *pFree; /* Free page blocks */
-} pcache = {0};
-
-/*
-** All code in this file should access the global pcache structure via the
-** alias "pcache_g". This ensures that the WSD emulation is used when
-** compiling for systems that do not support real WSD.
-*/
-#define pcache_g (GLOBAL(struct PCacheGlobal, pcache))
-
-/*
-** All global variables used by this module (all of which are grouped
-** 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".
-**
-** Some elements of the PCache and PgHdr structures are protected by the
-** SQLITE_MUTEX_STATUS_LRU mutex and other are not. The protected
-** elements are grouped at the end of the structures and are clearly
-** marked.
-**
-** Use the following macros must surround all access (read or write)
-** of protected elements. The mutex is not recursive and may not be
-** entered more than once. The pcacheMutexHeld() macro should only be
-** used within an assert() to verify that the mutex is being held.
-*/
-#define pcacheEnterMutex() sqlite3_mutex_enter(pcache_g.mutex)
-#define pcacheExitMutex() sqlite3_mutex_leave(pcache_g.mutex)
-#define pcacheMutexHeld() sqlite3_mutex_held(pcache_g.mutex)
-
-/*
** Some of the assert() macros in this code are too expensive to run
** even during normal debugging. Use them only rarely on long-running
** tests. Enable the expensive asserts using the
@@ -125,48 +49,6 @@
#if !defined(NDEBUG) && defined(SQLITE_ENABLE_EXPENSIVE_ASSERT)
/*
-** This routine verifies that the number of entries in the hash table
-** is pCache->nPage. This routine is used within assert() statements
-** only and is therefore disabled during production builds.
-*/
-static int pcacheCheckHashCount(PCache *pCache){
- int i;
- int nPage = 0;
- for(i=0; i<pCache->nHash; i++){
- PgHdr *p;
- for(p=pCache->apHash[i]; p; p=p->pNextHash){
- nPage++;
- }
- }
- assert( nPage==pCache->nPage );
- return 1;
-}
-#endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */
-
-
-#if !defined(NDEBUG) && defined(SQLITE_ENABLE_EXPENSIVE_ASSERT)
-/*
-** 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:
-**
-** expensive_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;
-}
-#endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */
-
-
-#if !defined(NDEBUG) && defined(SQLITE_ENABLE_EXPENSIVE_ASSERT)
-/*
** 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:
@@ -174,434 +56,86 @@
** expensive_assert( pcacheCheckSynced(pCache) );
*/
static int pcacheCheckSynced(PCache *pCache){
- PgHdr *p = pCache->pDirtyTail;
- for(p=pCache->pDirtyTail; p!=pCache->pSynced; p=p->pPrev){
+ PgHdr *p;
+ for(p=pCache->pDirtyTail; p!=pCache->pSynced; p=p->pDirtyPrev){
assert( p->nRef || (p->flags&PGHDR_NEED_SYNC) );
}
return (p==0 || p->nRef || (p->flags&PGHDR_NEED_SYNC)==0);
}
#endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */
-
-
/*
-** Remove a page from its hash table (PCache.apHash[]).
+** Remove page pPage from the list of dirty pages.
*/
-static void pcacheRemoveFromHash(PgHdr *pPage){
- assert( pcacheMutexHeld() );
- if( pPage->pPrevHash ){
- pPage->pPrevHash->pNextHash = pPage->pNextHash;
- }else{
- PCache *pCache = pPage->pCache;
- u32 h = pPage->pgno % pCache->nHash;
- assert( pCache->apHash[h]==pPage );
- pCache->apHash[h] = pPage->pNextHash;
- }
- if( pPage->pNextHash ){
- pPage->pNextHash->pPrevHash = pPage->pPrevHash;
- }
- pPage->pCache->nPage--;
- expensive_assert( pcacheCheckHashCount(pPage->pCache) );
-}
+static void pcacheRemoveFromDirtyList(PgHdr *pPage){
+ PCache *p = pPage->pCache;
-/*
-** Insert a page into the hash table
-**
-** The mutex must be held by the caller.
-*/
-static void pcacheAddToHash(PgHdr *pPage){
- PCache *pCache = pPage->pCache;
- u32 h = pPage->pgno % pCache->nHash;
- assert( pcacheMutexHeld() );
- pPage->pNextHash = pCache->apHash[h];
- pPage->pPrevHash = 0;
- if( pCache->apHash[h] ){
- pCache->apHash[h]->pPrevHash = pPage;
- }
- pCache->apHash[h] = pPage;
- pCache->nPage++;
- expensive_assert( pcacheCheckHashCount(pCache) );
-}
+ assert( pPage->pDirtyNext || pPage==p->pDirtyTail );
+ assert( pPage->pDirtyPrev || pPage==p->pDirty );
-/*
-** Attempt to increase the size the hash table to contain
-** at least nHash buckets.
-*/
-static int pcacheResizeHash(PCache *pCache, int nHash){
- PgHdr *p;
- PgHdr **pNew;
- assert( pcacheMutexHeld() );
-#ifdef SQLITE_MALLOC_SOFT_LIMIT
- if( nHash*sizeof(PgHdr*)>SQLITE_MALLOC_SOFT_LIMIT ){
- nHash = SQLITE_MALLOC_SOFT_LIMIT/sizeof(PgHdr *);
- }
-#endif
- pcacheExitMutex();
- pNew = (PgHdr **)sqlite3Malloc(sizeof(PgHdr*)*nHash);
- pcacheEnterMutex();
- if( !pNew ){
- return SQLITE_NOMEM;
- }
- memset(pNew, 0, sizeof(PgHdr *)*nHash);
- sqlite3_free(pCache->apHash);
- pCache->apHash = pNew;
- pCache->nHash = nHash;
- pCache->nPage = 0;
-
- for(p=pCache->pClean; p; p=p->pNext){
- pcacheAddToHash(p);
- }
- for(p=pCache->pDirty; p; p=p->pNext){
- pcacheAddToHash(p);
- }
- return SQLITE_OK;
-}
-
-/*
-** Remove a page from a linked list that is headed by *ppHead.
-** *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 );
- assert( pcacheMutexHeld() || ppHead!=&pPage->pCache->pClean );
-
- if( pPage->pPrev ){
- pPage->pPrev->pNext = pPage->pNext;
- }else{
- assert( *ppHead==pPage );
- *ppHead = pPage->pNext;
- }
- 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;
+ /* Update the PCache1.pSynced variable if necessary. */
+ if( p->pSynced==pPage ){
+ PgHdr *pSynced = pPage->pDirtyPrev;
+ while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){
+ pSynced = pSynced->pDirtyPrev;
}
- if( pCache->pSynced==pPage ){
- PgHdr *pSynced = pPage->pPrev;
- while( pSynced && (pSynced->flags&PGHDR_NEED_SYNC) ){
- pSynced = pSynced->pPrev;
- }
- pCache->pSynced = pSynced;
- }
+ p->pSynced = pSynced;
}
-}
-/*
-** Add a page from a linked list that is headed by *ppHead.
-** *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_g.mutex) );
- assert( (pPage->flags&PGHDR_DIRTY)==0 );
- if( pPage->pCache->bPurgeable==0 ) return;
- if( pPage->pNextLru ){
- assert( pcache_g.pLruTail!=pPage );
- pPage->pNextLru->pPrevLru = pPage->pPrevLru;
+ if( pPage->pDirtyNext ){
+ pPage->pDirtyNext->pDirtyPrev = pPage->pDirtyPrev;
}else{
- assert( pcache_g.pLruTail==pPage );
- pcache_g.pLruTail = pPage->pPrevLru;
+ assert( pPage==p->pDirtyTail );
+ p->pDirtyTail = pPage->pDirtyPrev;
}
- if( pPage->pPrevLru ){
- assert( pcache_g.pLruHead!=pPage );
- pPage->pPrevLru->pNextLru = pPage->pNextLru;
+ if( pPage->pDirtyPrev ){
+ pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext;
}else{
- assert( pcache_g.pLruHead==pPage );
- pcache_g.pLruHead = pPage->pNextLru;
+ assert( pPage==p->pDirty );
+ p->pDirty = pPage->pDirtyNext;
}
+ pPage->pDirtyNext = 0;
+ pPage->pDirtyPrev = 0;
+
+ expensive_assert( pcacheCheckSynced(p) );
}
/*
-** Add a page to the global LRU list. The page is normally added
-** to the front of the list so that it will be the last page recycled.
-** However, if the PGHDR_REUSE_UNLIKELY bit is set, the page is added
-** to the end of the LRU list so that it will be the next to be recycled.
+** Add page pPage to the head of the dirty list (PCache1.pDirty is set to
+** pPage).
*/
-static void pcacheAddToLruList(PgHdr *pPage){
- assert( sqlite3_mutex_held(pcache_g.mutex) );
- assert( (pPage->flags&PGHDR_DIRTY)==0 );
- if( pPage->pCache->bPurgeable==0 ) return;
- if( pcache_g.pLruTail && (pPage->flags & PGHDR_REUSE_UNLIKELY)!=0 ){
- /* If reuse is unlikely. Put the page at the end of the LRU list
- ** where it will be recycled sooner rather than later.
- */
- assert( pcache_g.pLruHead );
- pPage->pNextLru = 0;
- pPage->pPrevLru = pcache_g.pLruTail;
- pcache_g.pLruTail->pNextLru = pPage;
- pcache_g.pLruTail = pPage;
- pPage->flags &= ~PGHDR_REUSE_UNLIKELY;
- }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_g.pLruHead ){
- pcache_g.pLruHead->pPrevLru = pPage;
- }
- pPage->pNextLru = pcache_g.pLruHead;
- pcache_g.pLruHead = pPage;
- pPage->pPrevLru = 0;
- if( pcache_g.pLruTail==0 ){
- pcache_g.pLruTail = pPage;
- }
- }
-}
+static void pcacheAddToDirtyList(PgHdr *pPage){
+ PCache *p = pPage->pCache;
-/*********************************************** Memory Allocation ***********
-**
-** Initialize the page cache memory pool.
-**
-** This must be called at start-time when no page cache lines are
-** checked out. This function is not threadsafe.
-*/
-void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){
- PgFreeslot *p;
- sz &= ~7;
- pcache_g.szSlot = sz;
- pcache_g.pStart = pBuf;
- pcache_g.pFree = 0;
- while( n-- ){
- p = (PgFreeslot*)pBuf;
- p->pNext = pcache_g.pFree;
- pcache_g.pFree = p;
- pBuf = (void*)&((char*)pBuf)[sz];
+ assert( pPage->pDirtyNext==0 && pPage->pDirtyPrev==0 && p->pDirty!=pPage );
+
+ pPage->pDirtyNext = p->pDirty;
+ if( pPage->pDirtyNext ){
+ assert( pPage->pDirtyNext->pDirtyPrev==0 );
+ pPage->pDirtyNext->pDirtyPrev = pPage;
}
- pcache_g.pEnd = pBuf;
+ p->pDirty = pPage;
+ if( !p->pDirtyTail ){
+ p->pDirtyTail = pPage;
+ }
+ if( !p->pSynced && 0==(pPage->flags&PGHDR_NEED_SYNC) ){
+ p->pSynced = pPage;
+ }
+ expensive_assert( pcacheCheckSynced(p) );
}
/*
-** Allocate a page cache line. Look in the page cache memory pool first
-** and use an element from it first if available. If nothing is available
-** in the page cache memory pool, go to the general purpose memory allocator.
+** Wrapper around the pluggable caches xUnpin method. If the cache is
+** being used for an in-memory database, this function is a no-op.
*/
-static void *pcacheMalloc(int sz, PCache *pCache){
- assert( sqlite3_mutex_held(pcache_g.mutex) );
- if( sz<=pcache_g.szSlot && pcache_g.pFree ){
- PgFreeslot *p = pcache_g.pFree;
- pcache_g.pFree = p->pNext;
- sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, sz);
- sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1);
- return (void*)p;
- }else{
- void *p;
-
- /* Allocate a new buffer using sqlite3Malloc. Before doing so, exit the
- ** global pcache mutex and unlock the pager-cache object pCache. This is
- ** so that if the attempt to allocate a new buffer causes the the
- ** configured soft-heap-limit to be breached, it will be possible to
- ** reclaim memory from this pager-cache.
- */
- pcacheExitMutex();
- p = sqlite3Malloc(sz);
- pcacheEnterMutex();
-
- if( p ){
- sz = sqlite3MallocSize(p);
- sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz);
- }
- return p;
- }
-}
-void *sqlite3PageMalloc(int sz){
- void *p;
- pcacheEnterMutex();
- p = pcacheMalloc(sz, 0);
- pcacheExitMutex();
- return p;
-}
-
-/*
-** Release a pager memory allocation
-*/
-static void pcacheFree(void *p){
- assert( sqlite3_mutex_held(pcache_g.mutex) );
- if( p==0 ) return;
- if( p>=pcache_g.pStart && p<pcache_g.pEnd ){
- PgFreeslot *pSlot;
- sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, -1);
- pSlot = (PgFreeslot*)p;
- pSlot->pNext = pcache_g.pFree;
- pcache_g.pFree = pSlot;
- }else{
- int iSize = sqlite3MallocSize(p);
- sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -iSize);
- sqlite3_free(p);
- }
-}
-void sqlite3PageFree(void *p){
- pcacheEnterMutex();
- pcacheFree(p);
- pcacheExitMutex();
-}
-
-/*
-** Allocate a new page.
-*/
-static PgHdr *pcachePageAlloc(PCache *pCache){
- PgHdr *p;
- int sz = sizeof(*p) + pCache->szPage + pCache->szExtra;
- assert( sqlite3_mutex_held(pcache_g.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];
+static void pcacheUnpin(PgHdr *p){
+ PCache *pCache = p->pCache;
if( pCache->bPurgeable ){
- pcache_g.nCurrentPage++;
- }
- return p;
-}
-
-/*
-** Deallocate a page
-*/
-static void pcachePageFree(PgHdr *p){
- assert( sqlite3_mutex_held(pcache_g.mutex) );
- if( p->pCache->bPurgeable ){
- pcache_g.nCurrentPage--;
- }
- pcacheFree(p);
-}
-
-#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
-/*
-** Return the number of bytes that will be returned to the heap when
-** the argument is passed to pcachePageFree().
-*/
-static int pcachePageSize(PgHdr *p){
- assert( sqlite3_mutex_held(pcache_g.mutex) );
- assert( !pcache_g.pStart );
- assert( p && p->pCache );
- return sqlite3MallocSize(p);
-}
-#endif
-
-/*
-** 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(void){
- PgHdr *p = 0;
- assert( sqlite3_mutex_held(pcache_g.mutex) );
-
- if( (p=pcache_g.pLruTail)!=0 ){
- assert( (p->flags&PGHDR_DIRTY)==0 );
- pcacheRemoveFromLruList(p);
- pcacheRemoveFromHash(p);
- pcacheRemoveFromList(&p->pCache->pClean, p);
- }
-
- return p;
-}
-
-/*
-** 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_g.isInit );
- assert( sqlite3_mutex_held(pcache_g.mutex) );
-
- *ppPage = 0;
-
- /* If we have reached either the global or the local 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_g.nMaxPage+pCache->nMin-pcache_g.nMinPage)
- || pCache->nPinned>=pCache->nMax)
- ){
- 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( p->pgno==1 ){
+ pCache->pPage1 = 0;
}
- if( pPg ){
- int rc;
- pcacheExitMutex();
- rc = pCache->xStress(pCache->pStress, pPg);
- pcacheEnterMutex();
- if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
- return rc;
- }
- }
+ sqlite3GlobalConfig.pcache.xUnpin(pCache->pCache, p, 0);
}
-
- /* If either the local or the global page limit has been reached,
- ** try to recycle a page.
- */
- if( pCache->bPurgeable && (pCache->nPage>=pCache->nMax-1 ||
- pcache_g.nCurrentPage>=pcache_g.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);
}
/*************************************************** General Interfaces ******
@@ -610,19 +144,15 @@
** functions are threadsafe.
*/
int sqlite3PcacheInitialize(void){
- assert( pcache_g.isInit==0 );
- memset(&pcache_g, 0, sizeof(pcache));
- if( sqlite3GlobalConfig.bCoreMutex ){
- /* No need to check the return value of sqlite3_mutex_alloc().
- ** Allocating a static mutex cannot fail.
- */
- pcache_g.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU);
+ if( sqlite3GlobalConfig.pcache.xInit==0 ){
+ sqlite3PCacheSetDefault();
}
- pcache_g.isInit = 1;
- return SQLITE_OK;
+ return sqlite3GlobalConfig.pcache.xInit(sqlite3GlobalConfig.pcache.pArg);
}
void sqlite3PcacheShutdown(void){
- memset(&pcache_g, 0, sizeof(pcache));
+ if( sqlite3GlobalConfig.pcache.xShutdown ){
+ sqlite3GlobalConfig.pcache.xShutdown(sqlite3GlobalConfig.pcache.pArg);
+ }
}
/*
@@ -631,8 +161,10 @@
int sqlite3PcacheSize(void){ return sizeof(PCache); }
/*
-** Create a new PCache object. Storage space to hold the object
-** has already been allocated and is passed in as the p pointer.
+** Create a new PCache object. Storage space to hold the object
+** has already been allocated and is passed in as the p pointer.
+** The caller discovers how much space needs to be allocated by
+** calling sqlite3PcacheSize().
*/
void sqlite3PcacheOpen(
int szPage, /* Size of every page */
@@ -642,7 +174,6 @@
void *pStress, /* Argument to xStress */
PCache *p /* Preallocated space for the PCache */
){
- assert( pcache_g.isInit );
memset(p, 0, sizeof(PCache));
p->szPage = szPage;
p->szExtra = szExtra;
@@ -651,22 +182,18 @@
p->pStress = pStress;
p->nMax = 100;
p->nMin = 10;
-
- pcacheEnterMutex();
- if( bPurgeable ){
- pcache_g.nMaxPage += p->nMax;
- pcache_g.nMinPage += p->nMin;
- }
-
- pcacheExitMutex();
}
/*
-** Change the page size for PCache object. This can only happen
-** when the cache is empty.
+** Change the page size for PCache object. The caller must ensure that there
+** are no outstanding page references when this function is called.
*/
void sqlite3PcacheSetPageSize(PCache *pCache, int szPage){
- assert(pCache->nPage==0);
+ assert( pCache->nRef==0 && pCache->pDirty==0 );
+ if( pCache->pCache ){
+ sqlite3GlobalConfig.pcache.xDestroy(pCache->pCache);
+ pCache->pCache = 0;
+ }
pCache->szPage = szPage;
}
@@ -679,66 +206,82 @@
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;
+ int eCreate;
- assert( pcache_g.isInit );
assert( pCache!=0 );
assert( pgno>0 );
- expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) );
- pcacheEnterMutex();
+ /* If the pluggable cache (sqlite3_pcache*) has not been allocated,
+ ** allocate it now.
+ */
+ if( !pCache->pCache && createFlag ){
+ sqlite3_pcache *p;
+ int nByte;
+ nByte = pCache->szPage + pCache->szExtra + sizeof(PgHdr);
+ p = sqlite3GlobalConfig.pcache.xCreate(nByte, pCache->bPurgeable);
+ if( !p ){
+ return SQLITE_NOMEM;
+ }
+ sqlite3GlobalConfig.pcache.xCachesize(p, pCache->nMax);
+ pCache->pCache = p;
+ }
- /* 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;
+ eCreate = createFlag ? 1 : 0;
+ if( eCreate && (!pCache->bPurgeable || !pCache->pDirty) ){
+ eCreate = 2;
+ }
+ if( pCache->pCache ){
+ pPage = sqlite3GlobalConfig.pcache.xFetch(pCache->pCache, pgno, eCreate);
+ }
+
+ if( !pPage && eCreate==1 ){
+ PgHdr *pPg;
+
+ /* Find a dirty page to write-out and recycle. First try to find a
+ ** page that does not require a journal-sync (one with PGHDR_NEED_SYNC
+ ** cleared), but if that is not possible settle for any other
+ ** unreferenced dirty page.
+ */
+ expensive_assert( pcacheCheckSynced(pCache) );
+ for(pPg=pCache->pSynced;
+ pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC));
+ pPg=pPg->pDirtyPrev
+ );
+ if( !pPg ){
+ for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev);
+ }
+ if( pPg ){
+ int rc;
+ rc = pCache->xStress(pCache->pStress, pPg);
+ if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
+ return rc;
}
}
+
+ pPage = sqlite3GlobalConfig.pcache.xFetch(pCache->pCache, pgno, 2);
}
- 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;
+ if( pPage ){
+ if( 0==pPage->nRef ){
pCache->nRef++;
- pCache->nPinned++;
- pcacheAddToList(&pCache->pClean, pPage);
- pcacheAddToHash(pPage);
+ }
+ pPage->nRef++;
+ pPage->pData = (void*)&pPage[1];
+ pPage->pExtra = (void*)&((char*)pPage->pData)[pCache->szPage];
+ pPage->pCache = pCache;
+ pPage->pgno = pgno;
+ if( pgno==1 ){
+ pCache->pPage1 = pPage;
}
}
-
- pcacheExitMutex();
-
*ppPage = pPage;
- expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) );
- assert( pPage || !createFlag || rc!=SQLITE_OK );
- return rc;
+ return (pPage==0 && eCreate) ? SQLITE_NOMEM : SQLITE_OK;
}
/*
-** Dereference a page. When the reference count reaches zero,
-** move the page to the LRU list if it is clean.
+** Decrement the reference count on a page. If the page is clean and the
+** reference count drops to 0, then it is made elible for recycling.
*/
void sqlite3PcacheRelease(PgHdr *p){
assert( p->nRef>0 );
@@ -747,24 +290,18 @@
PCache *pCache = p->pCache;
pCache->nRef--;
if( (p->flags&PGHDR_DIRTY)==0 ){
- pCache->nPinned--;
- pcacheEnterMutex();
- if( pcache_g.nCurrentPage>pcache_g.nMaxPage ){
- pcacheRemoveFromList(&pCache->pClean, p);
- pcacheRemoveFromHash(p);
- pcachePageFree(p);
- }else{
- pcacheAddToLruList(p);
- }
- pcacheExitMutex();
+ pcacheUnpin(p);
}else{
- /* Move the page to the head of the caches dirty list. */
- pcacheRemoveFromList(&pCache->pDirty, p);
- pcacheAddToList(&pCache->pDirty, p);
+ /* Move the page to the head of the dirty list. */
+ pcacheRemoveFromDirtyList(p);
+ pcacheAddToDirtyList(p);
}
}
}
+/*
+** Increase the reference count of a supplied page by 1.
+*/
void sqlite3PcacheRef(PgHdr *p){
assert(p->nRef>0);
p->nRef++;
@@ -778,57 +315,43 @@
void sqlite3PcacheDrop(PgHdr *p){
PCache *pCache;
assert( p->nRef==1 );
- assert( 0==(p->flags&PGHDR_DIRTY) );
+ if( p->flags&PGHDR_DIRTY ){
+ pcacheRemoveFromDirtyList(p);
+ }
pCache = p->pCache;
pCache->nRef--;
- pCache->nPinned--;
- pcacheEnterMutex();
- pcacheRemoveFromList(&pCache->pClean, p);
- pcacheRemoveFromHash(p);
- pcachePageFree(p);
- pcacheExitMutex();
+ if( p->pgno==1 ){
+ pCache->pPage1 = 0;
+ }
+ sqlite3GlobalConfig.pcache.xUnpin(pCache->pCache, p, 1);
}
/*
-** Make sure the page is marked as dirty. If it isn't dirty already,
+** Make sure the page is marked as dirty. If it isn't dirty already,
** make it so.
*/
void sqlite3PcacheMakeDirty(PgHdr *p){
PCache *pCache;
p->flags &= ~PGHDR_DONT_WRITE;
- if( p->flags & PGHDR_DIRTY ) return;
- assert( (p->flags & PGHDR_DIRTY)==0 );
assert( p->nRef>0 );
- pCache = p->pCache;
- pcacheEnterMutex();
- pcacheRemoveFromList(&pCache->pClean, p);
- pcacheAddToList(&pCache->pDirty, p);
- pcacheExitMutex();
- p->flags |= PGHDR_DIRTY;
-}
-
-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--;
+ if( 0==(p->flags & PGHDR_DIRTY) ){
+ pCache = p->pCache;
+ p->flags |= PGHDR_DIRTY;
+ pcacheAddToDirtyList( p);
}
- expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) );
}
/*
-** Make sure the page is marked as clean. If it isn't clean already,
+** Make sure the page is marked as clean. If it isn't clean already,
** make it so.
*/
void sqlite3PcacheMakeClean(PgHdr *p){
if( (p->flags & PGHDR_DIRTY) ){
- pcacheEnterMutex();
- pcacheMakeClean(p);
- pcacheExitMutex();
+ pcacheRemoveFromDirtyList(p);
+ p->flags &= ~(PGHDR_DIRTY|PGHDR_NEED_SYNC);
+ if( p->nRef==0 ){
+ pcacheUnpin(p);
+ }
}
}
@@ -837,110 +360,62 @@
*/
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--;
- }
+ sqlite3PcacheMakeClean(p);
}
- sqlite3PcacheAssertFlags(pCache, 0, PGHDR_DIRTY);
- expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) );
- pcacheExitMutex();
}
/*
-** Change the page number of page p to newPgno. If newPgno is 0, then the
-** page object is added to the clean-list and the PGHDR_REUSE_UNLIKELY
-** flag set.
+** Clear the PGHDR_NEED_SYNC flag from all dirty pages.
+*/
+void sqlite3PcacheClearSyncFlags(PCache *pCache){
+ PgHdr *p;
+ for(p=pCache->pDirty; p; p=p->pDirtyNext){
+ p->flags &= ~PGHDR_NEED_SYNC;
+ }
+ pCache->pSynced = pCache->pDirtyTail;
+}
+
+/*
+** Change the page number of page p to newPgno.
*/
void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){
+ PCache *pCache = p->pCache;
assert( p->nRef>0 );
- pcacheEnterMutex();
- pcacheRemoveFromHash(p);
+ assert( newPgno>0 );
+ sqlite3GlobalConfig.pcache.xRekey(pCache->pCache, p, p->pgno, newPgno);
p->pgno = newPgno;
- if( newPgno==0 ){
- if( (p->flags & PGHDR_DIRTY) ){
- pcacheMakeClean(p);
- }
- p->flags = PGHDR_REUSE_UNLIKELY;
+ if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){
+ pcacheRemoveFromDirtyList(p);
+ pcacheAddToDirtyList(p);
}
- pcacheAddToHash(p);
- pcacheExitMutex();
}
/*
-** Remove all content from a page cache
-*/
-static void pcacheClear(PCache *pCache){
- PgHdr *p, *pNext;
- assert( sqlite3_mutex_held(pcache_g.mutex) );
- for(p=pCache->pClean; p; p=pNext){
- pNext = p->pNext;
- pcacheRemoveFromLruList(p);
- pcachePageFree(p);
- }
- for(p=pCache->pDirty; p; p=pNext){
- pNext = p->pNext;
- 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]));
-}
-
-
-/*
-** Drop every cache entry whose page number is greater than "pgno".
+** Drop every cache entry whose page number is greater than "pgno". The
+** caller must ensure that there are no outstanding references to any pages
+** other than page 1 with a page number greater than pgno.
+**
+** If there is a reference to page 1 and the pgno parameter passed to this
+** function is 0, then the data area associated with page 1 is zeroed, but
+** the page object is not dropped.
*/
void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
- PgHdr *p, *pNext;
- PgHdr *pDirty = pCache->pDirty;
- pcacheEnterMutex();
- for(p=pCache->pClean; p||pDirty; p=pNext){
- if( !p ){
- p = pDirty;
- pDirty = 0;
- }
- pNext = p->pNext;
- if( p->pgno>pgno ){
- if( p->nRef==0 ){
- pcacheRemoveFromHash(p);
- if( p->flags&PGHDR_DIRTY ){
- pcacheRemoveFromList(&pCache->pDirty, p);
- pCache->nPinned--;
- }else{
- pcacheRemoveFromList(&pCache->pClean, p);
- pcacheRemoveFromLruList(p);
- }
- pcachePageFree(p);
- }else{
- /* If there are references to the page, it cannot be freed. In this
- ** case, zero the page content instead.
- */
- memset(p->pData, 0, pCache->szPage);
+ if( pCache->pCache ){
+ PgHdr *p;
+ PgHdr *pNext;
+ for(p=pCache->pDirty; p; p=pNext){
+ pNext = p->pDirtyNext;
+ if( p->pgno>pgno ){
+ assert( p->flags&PGHDR_DIRTY );
+ sqlite3PcacheMakeClean(p);
}
}
- }
- pcacheExitMutex();
-}
-
-/*
-** If there are currently more than pcache.nMaxPage pages allocated, try
-** to recycle pages to reduce the number allocated to pcache.nMaxPage.
-*/
-static void pcacheEnforceMaxPage(void){
- PgHdr *p;
- assert( sqlite3_mutex_held(pcache_g.mutex) );
- while( pcache_g.nCurrentPage>pcache_g.nMaxPage
- && (p = pcacheRecyclePage())!=0 ){
- pcachePageFree(p);
+ if( pgno==0 && pCache->pPage1 ){
+ memset(pCache->pPage1->pData, 0, pCache->szPage);
+ pgno = 1;
+ }
+ sqlite3GlobalConfig.pcache.xTruncate(pCache->pCache, pgno+1);
}
}
@@ -948,51 +423,22 @@
** Close a cache.
*/
void sqlite3PcacheClose(PCache *pCache){
- pcacheEnterMutex();
-
- /* Free all the pages used by this pager and remove them from the LRU list. */
- pcacheClear(pCache);
- if( pCache->bPurgeable ){
- pcache_g.nMaxPage -= pCache->nMax;
- pcache_g.nMinPage -= pCache->nMin;
- pcacheEnforceMaxPage();
- }
- sqlite3_free(pCache->apHash);
- pcacheExitMutex();
-}
-
-
-#ifndef NDEBUG
-/*
-** Assert flags settings on all pages. Debugging only.
-*/
-void sqlite3PcacheAssertFlags(PCache *pCache, int trueMask, int falseMask){
- PgHdr *p;
- for(p=pCache->pDirty; p; p=p->pNext){
- assert( (p->flags&trueMask)==trueMask );
- assert( (p->flags&falseMask)==0 );
- }
- for(p=pCache->pClean; p; p=p->pNext){
- assert( (p->flags&trueMask)==trueMask );
- assert( (p->flags&falseMask)==0 );
+ if( pCache->pCache ){
+ sqlite3GlobalConfig.pcache.xDestroy(pCache->pCache);
}
}
-#endif
/*
** Discard the contents of the cache.
*/
int sqlite3PcacheClear(PCache *pCache){
- assert(pCache->nRef==0);
- pcacheEnterMutex();
- pcacheClear(pCache);
- pcacheExitMutex();
+ sqlite3PcacheTruncate(pCache, 0);
return SQLITE_OK;
}
/*
** Merge two lists of pages connected by pDirty and in pgno order.
-** Do not both fixing the pPrevDirty pointers.
+** Do not both fixing the pDirtyPrev pointers.
*/
static PgHdr *pcacheMergeDirtyList(PgHdr *pA, PgHdr *pB){
PgHdr result, *pTail;
@@ -1020,7 +466,7 @@
/*
** Sort the list of pages in accending order by pgno. Pages are
-** connected by pDirty pointers. The pPrevDirty pointers are
+** connected by pDirty pointers. The pDirtyPrev pointers are
** corrupted by this sort.
*/
#define N_SORT_BUCKET_ALLOC 25
@@ -1069,19 +515,22 @@
*/
PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
PgHdr *p;
- for(p=pCache->pDirty; p; p=p->pNext){
- p->pDirty = p->pNext;
+ for(p=pCache->pDirty; p; p=p->pDirtyNext){
+ p->pDirty = p->pDirtyNext;
}
return pcacheSortDirtyList(pCache->pDirty);
}
/*
-** Return the total number of outstanding page references.
+** Return the total number of referenced pages held by the cache.
*/
int sqlite3PcacheRefCount(PCache *pCache){
return pCache->nRef;
}
+/*
+** Return the number of references to the page supplied as an argument.
+*/
int sqlite3PcachePageRefcount(PgHdr *p){
return p->nRef;
}
@@ -1090,56 +539,15 @@
** Return the total number of pages in the cache.
*/
int sqlite3PcachePagecount(PCache *pCache){
- assert( pCache->nPage>=0 );
- return pCache->nPage;
-}
-
-#ifdef SQLITE_CHECK_PAGES
-/*
-** This function is used by the pager.c module to iterate through all
-** pages in the cache. At present, this is only required if the
-** SQLITE_CHECK_PAGES macro (used for debugging) is specified.
-*/
-void sqlite3PcacheIterate(PCache *pCache, void (*xIter)(PgHdr *)){
- PgHdr *p;
- for(p=pCache->pClean; p; p=p->pNext){
- xIter(p);
+ int nPage = 0;
+ if( pCache->pCache ){
+ nPage = sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache);
}
- for(p=pCache->pDirty; p; p=p->pNext){
- xIter(p);
- }
-}
-#endif
-
-/*
-** Set flags on all pages in the page cache
-*/
-void sqlite3PcacheClearFlags(PCache *pCache, int mask){
- PgHdr *p;
-
- /* Obtain the global mutex before modifying any PgHdr.flags variables
- ** or traversing the LRU list.
- */
- pcacheEnterMutex();
-
- mask = ~mask;
- for(p=pCache->pDirty; p; p=p->pNext){
- p->flags &= mask;
- }
- for(p=pCache->pClean; p; p=p->pNext){
- p->flags &= mask;
- }
-
- if( 0==(mask&PGHDR_NEED_SYNC) ){
- pCache->pSynced = pCache->pDirtyTail;
- assert( !pCache->pSynced || (pCache->pSynced->flags&PGHDR_NEED_SYNC)==0 );
- }
-
- pcacheExitMutex();
+ return nPage;
}
/*
-** Set the suggested cache-size value.
+** Get the suggested cache-size value.
*/
int sqlite3PcacheGetCachesize(PCache *pCache){
return pCache->nMax;
@@ -1149,60 +557,23 @@
** Set the suggested cache-size value.
*/
void sqlite3PcacheSetCachesize(PCache *pCache, int mxPage){
- if( mxPage<10 ){
- mxPage = 10;
- }
- if( pCache->bPurgeable ){
- pcacheEnterMutex();
- pcache_g.nMaxPage -= pCache->nMax;
- pcache_g.nMaxPage += mxPage;
- pcacheEnforceMaxPage();
- pcacheExitMutex();
- }
pCache->nMax = mxPage;
+ if( pCache->pCache ){
+ sqlite3GlobalConfig.pcache.xCachesize(pCache->pCache, mxPage);
+ }
}
-#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+#ifdef SQLITE_CHECK_PAGES
/*
-** This function is called to free superfluous dynamically allocated memory
-** held by the pager system. Memory in use by any SQLite pager allocated
-** by the current thread may be sqlite3_free()ed.
-**
-** nReq is the number of bytes of memory required. Once this much has
-** been released, the function returns. The return value is the total number
-** of bytes of memory released.
+** For all dirty pages currently in the cache, invoke the specified
+** callback. This is only used if the SQLITE_CHECK_PAGES macro is
+** defined.
*/
-int sqlite3PcacheReleaseMemory(int nReq){
- int nFree = 0;
- if( pcache_g.pStart==0 ){
- PgHdr *p;
- pcacheEnterMutex();
- while( (nReq<0 || nFree<nReq) && (p=pcacheRecyclePage()) ){
- nFree += pcachePageSize(p);
- pcachePageFree(p);
- }
- pcacheExitMutex();
+void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)){
+ PgHdr *pDirty;
+ for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext){
+ xIter(pDirty);
}
- return nFree;
-}
-#endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */
-
-#ifdef SQLITE_TEST
-void sqlite3PcacheStats(
- int *pnCurrent,
- int *pnMax,
- int *pnMin,
- int *pnRecyclable
-){
- PgHdr *p;
- int nRecyclable = 0;
- for(p=pcache_g.pLruHead; p; p=p->pNextLru){
- nRecyclable++;
- }
-
- *pnCurrent = pcache_g.nCurrentPage;
- *pnMax = pcache_g.nMaxPage;
- *pnMin = pcache_g.nMinPage;
- *pnRecyclable = nRecyclable;
}
#endif
+
diff --git a/src/pcache.h b/src/pcache.h
index 152d9e6..5834196 100644
--- a/src/pcache.h
+++ b/src/pcache.h
@@ -12,7 +12,7 @@
** This header file defines the interface that the sqlite page cache
** subsystem.
**
-** @(#) $Id: pcache.h,v 1.14 2008/10/17 18:51:53 danielk1977 Exp $
+** @(#) $Id: pcache.h,v 1.15 2008/11/13 14:28:29 danielk1977 Exp $
*/
#ifndef _PCACHE_H_
@@ -34,6 +34,7 @@
u32 pageHash; /* Hash of page content */
#endif
u16 flags; /* PGHDR flags defined below */
+
/**********************************************************************
** Elements above are public. All that follows is private to pcache.c
** and should not be accessed by other modules.
@@ -41,18 +42,11 @@
i16 nRef; /* Number of users of this page */
PCache *pCache; /* Cache that owns this page */
- /**********************************************************************
- ** Elements above are accessible at any time by the owner of the cache
- ** without the need for a mutex. The elements that follow can only be
- ** accessed while holding the SQLITE_MUTEX_STATIC_LRU mutex.
- */
- PgHdr *pNextHash, *pPrevHash; /* Hash collision chain for PgHdr.pgno */
- PgHdr *pNext, *pPrev; /* List of clean or dirty pages */
- PgHdr *pNextLru, *pPrevLru; /* Part of global LRU list */
+ PgHdr *pDirtyNext; /* Next element in list of dirty pages */
+ PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */
};
/* Bit values for PgHdr.flags */
-#define PGHDR_IN_JOURNAL 0x001 /* Page is in rollback journal */
#define PGHDR_DIRTY 0x002 /* Page has changed */
#define PGHDR_NEED_SYNC 0x004 /* Fsync the rollback journal before
** writing this page to the database */
@@ -116,14 +110,7 @@
void sqlite3PcacheClose(PCache*);
/* Clear flags from pages of the page cache */
-void sqlite3PcacheClearFlags(PCache*, int mask);
-
-/* Assert flags settings on all pages. Debugging only */
-#ifndef NDEBUG
- void sqlite3PcacheAssertFlags(PCache*, int trueMask, int falseMask);
-#else
-# define sqlite3PcacheAssertFlags(A,B,C)
-#endif
+void sqlite3PcacheClearSyncFlags(PCache *);
/* Return true if the number of dirty pages is 0 or 1 */
int sqlite3PcacheZeroOrOneDirtyPages(PCache*);
@@ -143,11 +130,11 @@
int sqlite3PcachePagecount(PCache*);
#ifdef SQLITE_CHECK_PAGES
-/* Iterate through all pages currently stored in the cache. This interface
-** is only available if SQLITE_CHECK_PAGES is defined when the library is
-** built.
+/* Iterate through all dirty pages currently stored in the cache. This
+** interface is only available if SQLITE_CHECK_PAGES is defined when the
+** library is built.
*/
-void sqlite3PcacheIterate(PCache *pCache, void (*xIter)(PgHdr *));
+void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *));
#endif
/* Set and get the suggested cache-size for the specified pager-cache.
@@ -168,4 +155,6 @@
void sqlite3PcacheStats(int*,int*,int*,int*);
#endif
+void sqlite3PCacheSetDefault(void);
+
#endif /* _PCACHE_H_ */
diff --git a/src/pcache1.c b/src/pcache1.c
new file mode 100644
index 0000000..bab07f4
--- /dev/null
+++ b/src/pcache1.c
@@ -0,0 +1,735 @@
+/*
+** 2008 November 05
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements the default page cache implementation (the
+** sqlite3_pcache interface). It also contains part of the implementation
+** of the SQLITE_CONFIG_PAGECACHE and sqlite3_release_memory() features.
+** If the default page cache implementation is overriden, then neither of
+** these two features are available.
+**
+** @(#) $Id: pcache1.c,v 1.1 2008/11/13 14:28:29 danielk1977 Exp $
+*/
+
+#include "sqliteInt.h"
+
+typedef struct PCache1 PCache1;
+typedef struct PgHdr1 PgHdr1;
+typedef struct PgFreeslot PgFreeslot;
+
+/* Pointers to structures of this type are cast and returned as
+** opaque sqlite3_pcache* handles
+*/
+struct PCache1 {
+ /* Cache configuration parameters. Page size (szPage) and the purgeable
+ ** flag (bPurgeable) are set when the cache is created. nMax may be
+ ** modified at any time by a call to the pcache1CacheSize() method.
+ ** The global mutex must be held when accessing nMax.
+ */
+ int szPage; /* Size of allocated pages in bytes */
+ int bPurgeable; /* True if cache is purgeable */
+ int nMin; /* Minimum number of pages reserved */
+ int nMax; /* Configured "cache_size" value */
+
+ /* Hash table of all pages. The following variables may only be accessed
+ ** when the accessor is holding the global mutex (see pcache1EnterMutex()
+ ** and pcache1LeaveMutex()).
+ */
+ int nRecyclable; /* Number of pages in the LRU list */
+ int nPage; /* Total number of pages in apHash */
+ int nHash; /* Number of slots in apHash[] */
+ PgHdr1 **apHash; /* Hash table for fast lookup by key */
+};
+
+/*
+** Each cache entry is represented by an instance of the following
+** structure. A buffer of PgHdr1.pCache->szPage bytes is allocated
+** directly after the structure in memory (see the PGHDR1_TO_PAGE()
+** macro below).
+*/
+struct PgHdr1 {
+ unsigned int iKey; /* Key value (page number) */
+ PgHdr1 *pNext; /* Next in hash table chain */
+ PCache1 *pCache; /* Cache that currently owns this page */
+ PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */
+ PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */
+};
+
+/*
+** Free slots in the allocator used to divide up the buffer provided using
+** the SQLITE_CONFIG_PAGECACHE mechanism.
+*/
+struct PgFreeslot {
+ PgFreeslot *pNext; /* Next free slot */
+};
+
+/*
+** Global data used by this cache.
+*/
+static SQLITE_WSD struct PCacheGlobal {
+ 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 */
+ PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */
+
+ /* Variables related to SQLITE_CONFIG_PAGECACHE settings. */
+ int szSlot; /* Size of each free slot */
+ void *pStart, *pEnd; /* Bounds of pagecache malloc range */
+ PgFreeslot *pFree; /* Free page blocks */
+} pcache1_g = {0};
+
+/*
+** All code in this file should access the global structure above via the
+** alias "pcache1". This ensures that the WSD emulation is used when
+** compiling for systems that do not support real WSD.
+*/
+#define pcache1 (GLOBAL(struct PCacheGlobal, pcache1_g))
+
+/*
+** When a PgHdr1 structure is allocated, the associated PCache1.szPage
+** bytes of data are located directly after it in memory (i.e. the total
+** size of the allocation is sizeof(PgHdr1)+PCache1.szPage byte). The
+** PGHDR1_TO_PAGE() macro takes a pointer to a PgHdr1 structure as
+** an argument and returns a pointer to the associated block of szPage
+** bytes. The PAGE_TO_PGHDR1() macro does the opposite: its argument is
+** a pointer to a block of szPage bytes of data and the return value is
+** a pointer to the associated PgHdr1 structure.
+**
+** assert( PGHDR1_TO_PAGE(PAGE_TO_PGHDR1(X))==X );
+*/
+#define PGHDR1_TO_PAGE(p) (void *)(&((unsigned char *)p)[sizeof(PgHdr1)])
+#define PAGE_TO_PGHDR1(p) (PgHdr1 *)(&((unsigned char *)p)[-1*sizeof(PgHdr1)])
+
+/*
+** Macros to enter and leave the global LRU mutex.
+*/
+#define pcache1EnterMutex() sqlite3_mutex_enter(pcache1.mutex)
+#define pcache1LeaveMutex() sqlite3_mutex_leave(pcache1.mutex)
+
+/******************************************************************************/
+/******** Page Allocation/SQLITE_CONFIG_PCACHE Related Functions **************/
+
+/*
+** This function is called during initialization if a static buffer is
+** supplied to use for the page-cache by passing the SQLITE_CONFIG_PAGECACHE
+** verb to sqlite3_config(). Parameter pBuf points to an allocation large
+** enough to contain 'n' buffers of 'sz' bytes each.
+*/
+void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){
+ PgFreeslot *p;
+ sz &= ~7;
+ pcache1.szSlot = sz;
+ pcache1.pStart = pBuf;
+ pcache1.pFree = 0;
+ while( n-- ){
+ p = (PgFreeslot*)pBuf;
+ p->pNext = pcache1.pFree;
+ pcache1.pFree = p;
+ pBuf = (void*)&((char*)pBuf)[sz];
+ }
+ pcache1.pEnd = pBuf;
+}
+
+/*
+** Malloc function used within this file to allocate space from the buffer
+** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no
+** such buffer exists or there is no space left in it, this function falls
+** back to sqlite3Malloc().
+*/
+static void *pcache1Alloc(int nByte){
+ void *p;
+ assert( sqlite3_mutex_held(pcache1.mutex) );
+ if( nByte<=pcache1.szSlot && pcache1.pFree ){
+ p = (PgHdr1 *)pcache1.pFree;
+ pcache1.pFree = pcache1.pFree->pNext;
+ sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte);
+ sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1);
+ }else{
+
+ /* Allocate a new buffer using sqlite3Malloc. Before doing so, exit the
+ ** global pcache mutex and unlock the pager-cache object pCache. This is
+ ** so that if the attempt to allocate a new buffer causes the the
+ ** configured soft-heap-limit to be breached, it will be possible to
+ ** reclaim memory from this pager-cache.
+ */
+ pcache1LeaveMutex();
+ p = sqlite3Malloc(nByte);
+ pcache1EnterMutex();
+ if( p ){
+ int sz = sqlite3MallocSize(p);
+ sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz);
+ }
+ }
+ return p;
+}
+
+/*
+** Free an allocated buffer obtained from pcache1Alloc().
+*/
+static void pcache1Free(void *p){
+ assert( sqlite3_mutex_held(pcache1.mutex) );
+ if( p==0 ) return;
+ if( p>=pcache1.pStart && p<pcache1.pEnd ){
+ PgFreeslot *pSlot;
+ sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, -1);
+ pSlot = (PgFreeslot*)p;
+ pSlot->pNext = pcache1.pFree;
+ pcache1.pFree = pSlot;
+ }else{
+ int iSize = sqlite3MallocSize(p);
+ sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -iSize);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Allocate a new page object initially associated with cache pCache.
+*/
+static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
+ int nByte = sizeof(PgHdr1) + pCache->szPage;
+ PgHdr1 *p = (PgHdr1 *)pcache1Alloc(nByte);
+ if( p ){
+ memset(p, 0, nByte);
+ if( pCache->bPurgeable ){
+ pcache1.nCurrentPage++;
+ }
+ }
+ return p;
+}
+
+/*
+** Free a page object allocated by pcache1AllocPage().
+*/
+static void pcache1FreePage(PgHdr1 *p){
+ if( p ){
+ if( p->pCache->bPurgeable ){
+ pcache1.nCurrentPage--;
+ }
+ pcache1Free(p);
+ }
+}
+
+/*
+** Malloc function used by SQLite to obtain space from the buffer configured
+** using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no such buffer
+** exists, this function falls back to sqlite3Malloc().
+*/
+void *sqlite3PageMalloc(int sz){
+ void *p;
+ pcache1EnterMutex();
+ p = pcache1Alloc(sz);
+ pcache1LeaveMutex();
+ return p;
+}
+
+/*
+** Free an allocated buffer obtained from sqlite3PageMalloc().
+*/
+void sqlite3PageFree(void *p){
+ pcache1EnterMutex();
+ pcache1Free(p);
+ pcache1LeaveMutex();
+}
+
+/******************************************************************************/
+/******** General Implementation Functions ************************************/
+
+/*
+** This function is used to resize the hash table used by the cache passed
+** as the first argument.
+**
+** The global mutex must be held when this function is called.
+*/
+static int pcache1ResizeHash(PCache1 *p){
+ PgHdr1 **apNew;
+ int nNew;
+ unsigned int i;
+
+ assert( sqlite3_mutex_held(pcache1.mutex) );
+
+ nNew = p->nHash*2;
+ if( nNew<256 ){
+ nNew = 256;
+ }
+
+ pcache1LeaveMutex();
+ apNew = (PgHdr1 **)sqlite3_malloc(sizeof(PgHdr1 *)*nNew);
+ pcache1EnterMutex();
+ if( apNew ){
+ memset(apNew, 0, sizeof(PgHdr1 *)*nNew);
+ for(i=0; i<p->nHash; i++){
+ PgHdr1 *pPage;
+ PgHdr1 *pNext = p->apHash[i];
+ while( (pPage = pNext) ){
+ unsigned int h = pPage->iKey % nNew;
+ pNext = pPage->pNext;
+ pPage->pNext = apNew[h];
+ apNew[h] = pPage;
+ }
+ }
+ sqlite3_free(p->apHash);
+ p->apHash = apNew;
+ p->nHash = nNew;
+ }
+
+ return (p->apHash ? SQLITE_OK : SQLITE_NOMEM);
+}
+
+/*
+** This function is used internally to remove the page pPage from the
+** global LRU list, if is part of it. If pPage is not part of the global
+** LRU list, then this function is a no-op.
+**
+** The global mutex must be held when this function is called.
+*/
+static void pcache1PinPage(PgHdr1 *pPage){
+ assert( sqlite3_mutex_held(pcache1.mutex) );
+ if( pPage && (pPage->pLruNext || pPage==pcache1.pLruTail) ){
+ if( pPage->pLruPrev ){
+ pPage->pLruPrev->pLruNext = pPage->pLruNext;
+ }
+ if( pPage->pLruNext ){
+ pPage->pLruNext->pLruPrev = pPage->pLruPrev;
+ }
+ if( pcache1.pLruHead==pPage ){
+ pcache1.pLruHead = pPage->pLruNext;
+ }
+ if( pcache1.pLruTail==pPage ){
+ pcache1.pLruTail = pPage->pLruPrev;
+ }
+ pPage->pLruNext = 0;
+ pPage->pLruPrev = 0;
+ pPage->pCache->nRecyclable--;
+ }
+}
+
+
+/*
+** Remove the page supplied as an argument from the hash table
+** (PCache1.apHash structure) that it is currently stored in.
+**
+** The global mutex must be held when this function is called.
+*/
+static void pcache1RemoveFromHash(PgHdr1 *pPage){
+ unsigned int h;
+ PCache1 *pCache = pPage->pCache;
+ PgHdr1 **pp;
+
+ h = pPage->iKey % pCache->nHash;
+ for(pp=&pCache->apHash[h]; (*pp)!=pPage; pp=&(*pp)->pNext);
+ *pp = (*pp)->pNext;
+
+ pCache->nPage--;
+}
+
+/*
+** If there are currently more than pcache.nMaxPage pages allocated, try
+** to recycle pages to reduce the number allocated to pcache.nMaxPage.
+*/
+static void pcache1EnforceMaxPage(void){
+ assert( sqlite3_mutex_held(pcache1.mutex) );
+ while( pcache1.nCurrentPage>pcache1.nMaxPage && pcache1.pLruTail ){
+ PgHdr1 *p = pcache1.pLruTail;
+ pcache1PinPage(p);
+ pcache1RemoveFromHash(p);
+ pcache1FreePage(p);
+ }
+}
+
+/*
+** Discard all pages from cache pCache with a page number (key value)
+** greater than or equal to iLimit. Any pinned pages that meet this
+** criteria are unpinned before they are discarded.
+**
+** The global mutex must be held when this function is called.
+*/
+static void pcache1TruncateUnsafe(
+ PCache1 *pCache,
+ unsigned int iLimit
+){
+ unsigned int h;
+ assert( sqlite3_mutex_held(pcache1.mutex) );
+ for(h=0; h<pCache->nHash; h++){
+ PgHdr1 **pp = &pCache->apHash[h];
+ PgHdr1 *pPage;
+ while( (pPage = *pp) ){
+ if( pPage->iKey>=iLimit ){
+ pcache1PinPage(pPage);
+ *pp = pPage->pNext;
+ pcache1FreePage(pPage);
+ }else{
+ pp = &pPage->pNext;
+ }
+ }
+ }
+}
+
+/******************************************************************************/
+/******** sqlite3_pcache Methods **********************************************/
+
+/*
+** Implementation of the sqlite3_pcache.xInit method.
+*/
+static int pcache1Init(void *pUnused){
+ memset(&pcache1, 0, sizeof(pcache1));
+ if( sqlite3GlobalConfig.bCoreMutex ){
+ pcache1.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Implementation of the sqlite3_pcache.xShutdown method.
+*/
+static void pcache1Shutdown(void *pUnused){
+ /* no-op */
+}
+
+/*
+** Implementation of the sqlite3_pcache.xCreate method.
+**
+** Allocate a new cache.
+*/
+static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){
+ PCache1 *pCache;
+
+ pCache = (PCache1 *)sqlite3_malloc(sizeof(PCache1));
+ if( pCache ){
+ memset(pCache, 0, sizeof(PCache1));
+ pCache->szPage = szPage;
+ pCache->bPurgeable = (bPurgeable ? 1 : 0);
+ if( bPurgeable ){
+ pCache->nMin = 10;
+ pcache1EnterMutex();
+ pcache1.nMinPage += pCache->nMin;
+ pcache1LeaveMutex();
+ }
+ }
+ return (sqlite3_pcache *)pCache;
+}
+
+/*
+** Implementation of the sqlite3_pcache.xCachesize method.
+**
+** Configure the cache_size limit for a cache.
+*/
+static void pcache1Cachesize(sqlite3_pcache *p, int nMax){
+ PCache1 *pCache = (PCache1 *)p;
+ if( pCache->bPurgeable ){
+ pcache1EnterMutex();
+ pcache1.nMaxPage += (nMax - pCache->nMax);
+ pCache->nMax = nMax;
+ pcache1EnforceMaxPage();
+ pcache1LeaveMutex();
+ }
+}
+
+/*
+** Implementation of the sqlite3_pcache.xPagecount method.
+*/
+static int pcache1Pagecount(sqlite3_pcache *p){
+ int n;
+ pcache1EnterMutex();
+ n = ((PCache1 *)p)->nPage;
+ pcache1LeaveMutex();
+ return n;
+}
+
+/*
+** Implementation of the sqlite3_pcache.xFetch method.
+**
+** Fetch a page by key value.
+**
+** Whether or not a new page may be allocated by this function depends on
+** the value of the createFlag argument.
+**
+** There are three different approaches to obtaining space for a page,
+** depending on the value of parameter createFlag (which may be 0, 1 or 2).
+**
+** 1. Regardless of the value of createFlag, the cache is searched for a
+** copy of the requested page. If one is found, it is returned.
+**
+** 2. If createFlag==0 and the page is not already in the cache, NULL is
+** returned.
+**
+** 3. If createFlag is 1, the cache is marked as purgeable and the page is
+** not already in the cache, and if either of the following are true,
+** return NULL:
+**
+** (a) the number of pages pinned by the cache is greater than
+** PCache1.nMax, or
+** (b) the number of pages pinned by the cache is greater than
+** the sum of nMax for all purgeable caches, less the sum of
+** nMin for all other purgeable caches.
+**
+** 4. If none of the first three conditions apply and the cache is marked
+** as purgeable, and if one of the following is true:
+**
+** (a) The number of pages allocated for the cache is already
+** PCache1.nMax, or
+**
+** (b) The number of pages allocated for all purgeable caches is
+** already equal to or greater than the sum of nMax for all
+** purgeable caches,
+**
+** then attempt to recycle a page from the LRU list. If it is the right
+** size, return the recycled buffer. Otherwise, free the buffer and
+** proceed to step 5.
+**
+** 5. Otherwise, allocate and return a new page buffer.
+*/
+static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){
+ int nPinned;
+ PCache1 *pCache = (PCache1 *)p;
+ PgHdr1 *pPage = 0;
+
+ pcache1EnterMutex();
+ if( createFlag==1 ) sqlite3BeginBenignMalloc();
+
+ /* Search the hash table for an existing entry. */
+ if( pCache->nHash>0 ){
+ unsigned int h = iKey % pCache->nHash;
+ for(pPage=pCache->apHash[h]; pPage&&pPage->iKey!=iKey; pPage=pPage->pNext);
+ }
+
+ if( pPage || createFlag==0 ){
+ pcache1PinPage(pPage);
+ goto fetch_out;
+ }
+
+ /* Step 3 of header comment. */
+ nPinned = pCache->nPage - pCache->nRecyclable;
+ if( createFlag==1 && pCache->bPurgeable && (
+ nPinned>=(pcache1.nMaxPage+pCache->nMin-pcache1.nMinPage)
+ || nPinned>=(pCache->nMax)
+ )){
+ goto fetch_out;
+ }
+
+ if( pCache->nPage>=pCache->nHash && pcache1ResizeHash(pCache) ){
+ goto fetch_out;
+ }
+
+ /* Step 4. Try to recycle a page buffer if appropriate. */
+ if( pCache->bPurgeable && pcache1.pLruTail && (
+ pCache->nPage>=pCache->nMax-1 || pcache1.nCurrentPage>=pcache1.nMaxPage
+ )){
+ pPage = pcache1.pLruTail;
+ pcache1RemoveFromHash(pPage);
+ pcache1PinPage(pPage);
+ if( pPage->pCache->szPage!=pCache->szPage ){
+ pcache1FreePage(pPage);
+ pPage = 0;
+ }else{
+ pcache1.nCurrentPage -= (pPage->pCache->bPurgeable - pCache->bPurgeable);
+ }
+ }
+
+ /* Step 5. If a usable page buffer has still not been found,
+ ** attempt to allocate a new one.
+ */
+ if( !pPage ){
+ pPage = pcache1AllocPage(pCache);
+ }
+
+ if( pPage ){
+ unsigned int h = iKey % pCache->nHash;
+ memset(pPage, 0, pCache->szPage + sizeof(PgHdr1));
+ pCache->nPage++;
+ pPage->iKey = iKey;
+ pPage->pNext = pCache->apHash[h];
+ pPage->pCache = pCache;
+ pCache->apHash[h] = pPage;
+ }
+
+fetch_out:
+ if( createFlag==1 ) sqlite3EndBenignMalloc();
+ pcache1LeaveMutex();
+ return (pPage ? PGHDR1_TO_PAGE(pPage) : 0);
+}
+
+
+/*
+** Implementation of the sqlite3_pcache.xUnpin method.
+**
+** Mark a page as unpinned (eligible for asynchronous recycling).
+*/
+static void pcache1Unpin(sqlite3_pcache *p, void *pPg, int reuseUnlikely){
+ PCache1 *pCache = (PCache1 *)p;
+ PgHdr1 *pPage = PAGE_TO_PGHDR1(pPg);
+
+ pcache1EnterMutex();
+
+ /* It is an error to call this function if the page is already
+ ** part of the global LRU list.
+ */
+ assert( pPage->pLruPrev==0 && pPage->pLruNext==0 );
+ assert( pcache1.pLruHead!=pPage && pcache1.pLruTail!=pPage );
+
+ if( reuseUnlikely || pcache1.nCurrentPage>pcache1.nMaxPage ){
+ pcache1RemoveFromHash(pPage);
+ pcache1FreePage(pPage);
+ }else{
+ /* Add the page to the global LRU list. Normally, the page is added to
+ ** the head of the list (last page to be recycled). However, if the
+ ** reuseUnlikely flag passed to this function is true, the page is added
+ ** to the tail of the list (first page to be recycled).
+ */
+ if( pcache1.pLruHead ){
+ pcache1.pLruHead->pLruPrev = pPage;
+ pPage->pLruNext = pcache1.pLruHead;
+ pcache1.pLruHead = pPage;
+ }else{
+ pcache1.pLruTail = pPage;
+ pcache1.pLruHead = pPage;
+ }
+ pCache->nRecyclable++;
+ }
+
+ pcache1LeaveMutex();
+}
+
+/*
+** Implementation of the sqlite3_pcache.xRekey method.
+*/
+static void pcache1Rekey(
+ sqlite3_pcache *p,
+ void *pPg,
+ unsigned int iOld,
+ unsigned int iNew
+){
+ PCache1 *pCache = (PCache1 *)p;
+ PgHdr1 *pPage = PAGE_TO_PGHDR1(pPg);
+ PgHdr1 **pp;
+ unsigned int h;
+ assert( pPage->iKey==iOld );
+
+ pcache1EnterMutex();
+
+ h = iOld%pCache->nHash;
+ pp = &pCache->apHash[h];
+ while( (*pp)!=pPage ){
+ pp = &(*pp)->pNext;
+ }
+ *pp = pPage->pNext;
+
+ h = iNew%pCache->nHash;
+ pPage->iKey = iNew;
+ pPage->pNext = pCache->apHash[h];
+ pCache->apHash[h] = pPage;
+
+ pcache1LeaveMutex();
+}
+
+/*
+** Implementation of the sqlite3_pcache.xTruncate method.
+**
+** Discard all unpinned pages in the cache with a page number equal to
+** or greater than parameter iLimit. Any pinned pages with a page number
+** equal to or greater than iLimit are implicitly unpinned.
+*/
+static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){
+ PCache1 *pCache = (PCache1 *)p;
+ pcache1EnterMutex();
+ pcache1TruncateUnsafe(pCache, iLimit);
+ pcache1LeaveMutex();
+}
+
+/*
+** Implementation of the sqlite3_pcache.xDestroy method.
+**
+** Destroy a cache allocated using pcache1Create().
+*/
+static void pcache1Destroy(sqlite3_pcache *p){
+ PCache1 *pCache = (PCache1 *)p;
+ pcache1EnterMutex();
+ pcache1TruncateUnsafe(pCache, 0);
+ pcache1.nMaxPage -= pCache->nMax;
+ pcache1.nMinPage -= pCache->nMin;
+ pcache1EnforceMaxPage();
+ pcache1LeaveMutex();
+ sqlite3_free(pCache->apHash);
+ sqlite3_free(pCache);
+}
+
+/*
+** This function is called during initialization (sqlite3_initialize()) to
+** install the default pluggable cache module, assuming the user has not
+** already provided an alternative.
+*/
+void sqlite3PCacheSetDefault(void){
+ static sqlite3_pcache_methods defaultMethods = {
+ 0, /* pArg */
+ pcache1Init, /* xInit */
+ pcache1Shutdown, /* xShutdown */
+ pcache1Create, /* xCreate */
+ pcache1Cachesize, /* xCachesize */
+ pcache1Pagecount, /* xPagecount */
+ pcache1Fetch, /* xFetch */
+ pcache1Unpin, /* xUnpin */
+ pcache1Rekey, /* xRekey */
+ pcache1Truncate, /* xTruncate */
+ pcache1Destroy /* xDestroy */
+ };
+ sqlite3_config(SQLITE_CONFIG_PCACHE, &defaultMethods);
+}
+
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
+/*
+** This function is called to free superfluous dynamically allocated memory
+** held by the pager system. Memory in use by any SQLite pager allocated
+** by the current thread may be sqlite3_free()ed.
+**
+** nReq is the number of bytes of memory required. Once this much has
+** been released, the function returns. The return value is the total number
+** of bytes of memory released.
+*/
+int sqlite3PcacheReleaseMemory(int nReq){
+ int nFree = 0;
+ if( pcache1.pStart==0 ){
+ PgHdr1 *p;
+ pcache1EnterMutex();
+ while( (nReq<0 || nFree<nReq) && (p=pcache1.pLruTail) ){
+ nFree += sqlite3MallocSize(p);
+ pcache1PinPage(p);
+ pcache1RemoveFromHash(p);
+ pcache1FreePage(p);
+ }
+ pcache1LeaveMutex();
+ }
+ return nFree;
+}
+#endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */
+
+#ifdef SQLITE_TEST
+/*
+** This function is used by test procedures to inspect the internal state
+** of the global cache.
+*/
+void sqlite3PcacheStats(
+ int *pnCurrent, /* OUT: Total number of pages cached */
+ int *pnMax, /* OUT: Global maximum cache size */
+ int *pnMin, /* OUT: Sum of PCache1.nMin for purgeable caches */
+ int *pnRecyclable /* OUT: Total number of pages available for recycling */
+){
+ PgHdr1 *p;
+ int nRecyclable = 0;
+ for(p=pcache1.pLruHead; p; p=p->pLruNext){
+ nRecyclable++;
+ }
+ *pnCurrent = pcache1.nCurrentPage;
+ *pnMax = pcache1.nMaxPage;
+ *pnMin = pcache1.nMinPage;
+ *pnRecyclable = nRecyclable;
+}
+#endif
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index b31567b..1e0dd74 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -30,7 +30,7 @@
** the version number) and changes its name to "sqlite3.h" as
** part of the build process.
**
-** @(#) $Id: sqlite.h.in,v 1.412 2008/11/10 23:54:06 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.413 2008/11/13 14:28:29 danielk1977 Exp $
*/
#ifndef _SQLITE3_H_
#define _SQLITE3_H_
@@ -1334,6 +1334,8 @@
#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
+#define SQLITE_CONFIG_PCACHE 14 /* sqlite3_pcache_methods* */
+#define SQLITE_CONFIG_GETPCACHE 15 /* sqlite3_pcache_methods* */
/*
** CAPI3REF: Configuration Options {H10170} <S20000>
@@ -6556,6 +6558,143 @@
#define SQLITE_STMTSTATUS_SORT 2
/*
+** The sqlite3_pcache type is opaque. It is implemented by
+** the pluggable module. The SQLite core has no knowledge of
+** its size or internal structure and never deals with the
+** sqlite3_pcache object except by holding and passing pointers
+** to the object.
+*/
+typedef struct sqlite3_pcache sqlite3_pcache;
+
+/*
+** CAPI3REF: Custom Page Cache API.
+** EXPERIMENTAL
+**
+** The sqlite3_config(SQLITE_CONFIG_SET_PCACHE, ...) interface can
+** register an alternative page cache implementation by passing in an
+** instance of the sqlite3_pcache_methods structure. The majority of the
+** heap memory used by sqlite is used by the page cache to cache data read
+** from, or ready to be written to, the database file. By implementing a
+** custom page cache using this API, an application can control more
+** precisely the amount of memory consumed by sqlite, the way in which
+** said memory is allocated and released, and the policies used to
+** determine exactly which parts of a database file are cached and for
+** how long.
+**
+** The contents of the structure are copied to an internal buffer by sqlite
+** within the call to [sqlite3_config].
+**
+** The xInit() method is called once for each call to sqlite3_initialize()
+** (usually only once during the lifetime of the process). It is passed
+** a copy of the sqlite3_pcache_methods.pArg value. It can be used to set
+** up global structures and mutexes required by the custom page cache
+** implementation. The xShutdown() method is called from within
+** sqlite3_shutdown(), if the application invokes this API. It can be used
+** to clean up any outstanding resources before process shutdown, if required.
+**
+** The xCreate() method is used to construct a new cache instance. The
+** first parameter, szPage, is the size in bytes of the pages that must
+** be allocated by the cache. szPage will not be a power of two. The
+** second argument, bPurgeable, is true if the cache being created will
+** be used to cache database pages read from a file stored on disk, or
+** false if it is used for an in-memory database. The cache implementation
+** does not have to do anything special based on the value of bPurgeable,
+** it is purely advisory.
+**
+** The xCachesize() method may be called at any time by SQLite to set the
+** suggested maximum cache-size (number of pages stored by) the cache
+** instance passed as the first argument. This is the value configured using
+** the SQLite "PRAGMA cache_size" command. As with the bPurgeable parameter,
+** the implementation is not required to do anything special with this
+** value, it is advisory only.
+**
+** The xPagecount() method should return the number of pages currently
+** stored in the cache supplied as an argument.
+**
+** The xFetch() method is used to fetch a page and return a pointer to it.
+** A 'page', in this context, is a buffer of szPage bytes aligned at an
+** 8-byte boundary. The page to be fetched is determined by the key. The
+** mimimum key value is 1. After it has been retrieved using xFetch, the page
+** is considered to be pinned.
+**
+** If the requested page is already in the page cache, then a pointer to
+** the cached buffer should be returned with its contents intact. If the
+** page is not already in the cache, then the expected behaviour of the
+** cache is determined by the value of the createFlag parameter passed
+** to xFetch, according to the following table:
+**
+** <table border=1 width=85% align=center>
+** <tr><th>createFlag<th>Expected Behaviour
+** <tr><td>0<td>NULL should be returned. No new cache entry is created.
+** <tr><td>1<td>If createFlag is set to 1, this indicates that
+** SQLite is holding pinned pages that can be unpinned
+** by writing their contents to the database file (a
+** relatively expensive operation). In this situation the
+** cache implementation has two choices: it can return NULL,
+** in which case SQLite will attempt to unpin one or more
+** pages before re-requesting the same page, or it can
+** allocate a new page and return a pointer to it. If a new
+** page is allocated, then it must be completely zeroed before
+** it is returned.
+** <tr><td>2<td>If createFlag is set to 2, then SQLite is not holding any
+** pinned pages associated with the specific cache passed
+** as the first argument to xFetch() that can be unpinned. The
+** cache implementation should attempt to allocate a new
+** cache entry and return a pointer to it. Again, the new
+** page should be zeroed before it is returned. If the xFetch()
+** method returns NULL when createFlag==2, SQLite assumes that
+** a memory allocation failed and returns SQLITE_NOMEM to the
+** user.
+** </table>
+**
+** xUnpin() is called by SQLite with a pointer to a currently pinned page
+** as its second argument. If the third parameter, discard, is non-zero,
+** then the page should be evicted from the cache. In this case SQLite
+** assumes that the next time the page is retrieved from the cache using
+** the xFetch() method, it will be zeroed. If the discard parameter is
+** zero, then the page is considered to be unpinned. The cache implementation
+** may choose to reclaim (free or recycle) unpinned pages at any time.
+** SQLite assumes that next time the page is retrieved from the cache
+** it will either be zeroed, or contain the same data that it did when it
+** was unpinned.
+**
+** The cache is not required to perform any reference counting. A single
+** call to xUnpin() unpins the page regardless of the number of prior calls
+** to xFetch().
+**
+** The xRekey() method is used to change the key value associated with the
+** page passed as the second argument from oldKey to newKey. If the cache
+** contains an entry associated with oldKey, it should be discarded. Any
+** cache entry associated with oldKey is guaranteed not to be pinned.
+**
+** When SQLite calls the xTruncate() method, the cache must discard all
+** existing cache entries with page numbers (keys) greater than or equal
+** to the value of the iLimit parameter passed to xTruncate(). If any
+** of these pages are pinned, they are implicitly unpinned, meaning that
+** they can be safely discarded.
+**
+** The xDestroy() method is used to delete a cache allocated by xCreate().
+** All resources associated with the specified cache should be freed. After
+** calling the xDestroy() method, SQLite considers the sqlite3_pcache*
+** handle invalid, and will not use it with any other sqlite3_pcache_methods
+** functions.
+*/
+typedef struct sqlite3_pcache_methods sqlite3_pcache_methods;
+struct sqlite3_pcache_methods {
+ void *pArg;
+ int (*xInit)(void*);
+ void (*xShutdown)(void*);
+ sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable);
+ void (*xCachesize)(sqlite3_pcache*, int nCachesize);
+ int (*xPagecount)(sqlite3_pcache*);
+ void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
+ void (*xUnpin)(sqlite3_pcache*, void*, int discard);
+ void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey);
+ void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
+ void (*xDestroy)(sqlite3_pcache*);
+};
+
+/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 9af75ec..c407f57 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
-** @(#) $Id: sqliteInt.h,v 1.790 2008/11/11 18:29:00 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.791 2008/11/13 14:28:30 danielk1977 Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
@@ -1958,6 +1958,7 @@
int nLookaside; /* Default lookaside buffer count */
sqlite3_mem_methods m; /* Low-level memory allocation interface */
sqlite3_mutex_methods mutex; /* Low-level mutex interface */
+ sqlite3_pcache_methods pcache; /* Low-level page-cache interface */
void *pHeap; /* Heap storage space */
int nHeap; /* Size of pHeap[] */
int mnReq, mxReq; /* Min and max heap requests sizes */