#include "sqliteInt.h"
struct PCache {
PgHdr *pDirty, *pDirtyTail;
PgHdr *pSynced;
i64 nRefSum;
int szCache;
int szSpill;
int szPage;
int szExtra;
u8 bPurgeable;
u8 eCreate;
int (*xStress)(void*,PgHdr*);
void *pStress;
sqlite3_pcache *pCache;
};
#if defined(SQLITE_DEBUG) && 0
int sqlite3PcacheTrace = 2;
int sqlite3PcacheMxDump = 9999;
# define pcacheTrace(X) if(sqlite3PcacheTrace){sqlite3DebugPrintf X;}
static void pcachePageTrace(int i, sqlite3_pcache_page *pLower){
PgHdr *pPg;
unsigned char *a;
int j;
if( pLower==0 ){
printf("%3d: NULL\n", i);
}else{
pPg = (PgHdr*)pLower->pExtra;
printf("%3d: nRef %2lld flgs %02x data ", i, pPg->nRef, pPg->flags);
a = (unsigned char *)pLower->pBuf;
for(j=0; j<12; j++) printf("%02x", a[j]);
printf(" ptr %p\n", pPg);
}
}
static void pcacheDump(PCache *pCache){
int N;
int i;
sqlite3_pcache_page *pLower;
if( sqlite3PcacheTrace<2 ) return;
if( pCache->pCache==0 ) return;
N = sqlite3PcachePagecount(pCache);
if( N>sqlite3PcacheMxDump ) N = sqlite3PcacheMxDump;
for(i=1; i<=N; i++){
pLower = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0);
pcachePageTrace(i, pLower);
if( pLower && ((PgHdr*)pLower)->pPage==0 ){
sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0);
}
}
}
#else
# define pcacheTrace(X)
# define pcachePageTrace(PGNO, X)
# define pcacheDump(X)
#endif
#ifdef SQLITE_DEBUG
static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){
PgHdr *p;
for(p=pCache->pDirty; p; p=p->pDirtyNext){
if( p==pPg ) return 1;
}
return 0;
}
#endif
#ifdef SQLITE_DEBUG
int sqlite3PcachePageSanity(PgHdr *pPg){
PCache *pCache;
assert( pPg!=0 );
assert( pPg->pgno>0 || pPg->pPager==0 );
pCache = pPg->pCache;
assert( pCache!=0 );
if( pPg->flags & PGHDR_CLEAN ){
assert( (pPg->flags & PGHDR_DIRTY)==0 );
assert( !pageOnDirtyList(pCache, pPg) );
}else{
assert( (pPg->flags & PGHDR_DIRTY)!=0 );
assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg );
assert( pPg->pDirtyPrev==0 || pPg->pDirtyPrev->pDirtyNext==pPg );
assert( pPg->pDirtyPrev!=0 || pCache->pDirty==pPg );
assert( pageOnDirtyList(pCache, pPg) );
}
if( pPg->flags & PGHDR_WRITEABLE ){
assert( pPg->flags & PGHDR_DIRTY );
}
return 1;
}
#endif
#define PCACHE_DIRTYLIST_REMOVE 1
#define PCACHE_DIRTYLIST_ADD 2
#define PCACHE_DIRTYLIST_FRONT 3
static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){
PCache *p = pPage->pCache;
pcacheTrace(("%p.DIRTYLIST.%s %d\n", p,
addRemove==1 ? "REMOVE" : addRemove==2 ? "ADD" : "FRONT",
pPage->pgno));
if( addRemove & PCACHE_DIRTYLIST_REMOVE ){
assert( pPage->pDirtyNext || pPage==p->pDirtyTail );
assert( pPage->pDirtyPrev || pPage==p->pDirty );
if( p->pSynced==pPage ){
p->pSynced = pPage->pDirtyPrev;
}
if( pPage->pDirtyNext ){
pPage->pDirtyNext->pDirtyPrev = pPage->pDirtyPrev;
}else{
assert( pPage==p->pDirtyTail );
p->pDirtyTail = pPage->pDirtyPrev;
}
if( pPage->pDirtyPrev ){
pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext;
}else{
assert( pPage==p->pDirty );
p->pDirty = pPage->pDirtyNext;
assert( p->bPurgeable || p->eCreate==2 );
if( p->pDirty==0 ){
assert( p->bPurgeable==0 || p->eCreate==1 );
p->eCreate = 2;
}
}
}
if( addRemove & PCACHE_DIRTYLIST_ADD ){
pPage->pDirtyPrev = 0;
pPage->pDirtyNext = p->pDirty;
if( pPage->pDirtyNext ){
assert( pPage->pDirtyNext->pDirtyPrev==0 );
pPage->pDirtyNext->pDirtyPrev = pPage;
}else{
p->pDirtyTail = pPage;
if( p->bPurgeable ){
assert( p->eCreate==2 );
p->eCreate = 1;
}
}
p->pDirty = pPage;
if( !p->pSynced
&& 0==(pPage->flags&PGHDR_NEED_SYNC)
){
p->pSynced = pPage;
}
}
pcacheDump(p);
}
static void pcacheUnpin(PgHdr *p){
if( p->pCache->bPurgeable ){
pcacheTrace(("%p.UNPIN %d\n", p->pCache, p->pgno));
sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 0);
pcacheDump(p->pCache);
}
}
static int numberOfCachePages(PCache *p){
if( p->szCache>=0 ){
return p->szCache;
}else{
i64 n;
n = ((-1024*(i64)p->szCache)/(p->szPage+p->szExtra));
if( n>1000000000 ) n = 1000000000;
return (int)n;
}
}
int sqlite3PcacheInitialize(void){
if( sqlite3GlobalConfig.pcache2.xInit==0 ){
sqlite3PCacheSetDefault();
assert( sqlite3GlobalConfig.pcache2.xInit!=0 );
}
return sqlite3GlobalConfig.pcache2.xInit(sqlite3GlobalConfig.pcache2.pArg);
}
void sqlite3PcacheShutdown(void){
if( sqlite3GlobalConfig.pcache2.xShutdown ){
sqlite3GlobalConfig.pcache2.xShutdown(sqlite3GlobalConfig.pcache2.pArg);
}
}
int sqlite3PcacheSize(void){ return sizeof(PCache); }
int sqlite3PcacheOpen(
int szPage,
int szExtra,
int bPurgeable,
int (*xStress)(void*,PgHdr*),
void *pStress,
PCache *p
){
memset(p, 0, sizeof(PCache));
p->szPage = 1;
p->szExtra = szExtra;
assert( szExtra>=8 );
p->bPurgeable = bPurgeable;
p->eCreate = 2;
p->xStress = xStress;
p->pStress = pStress;
p->szCache = 100;
p->szSpill = 1;
pcacheTrace(("%p.OPEN szPage %d bPurgeable %d\n",p,szPage,bPurgeable));
return sqlite3PcacheSetPageSize(p, szPage);
}
int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){
assert( pCache->nRefSum==0 && pCache->pDirty==0 );
if( pCache->szPage ){
sqlite3_pcache *pNew;
pNew = sqlite3GlobalConfig.pcache2.xCreate(
szPage, pCache->szExtra + ROUND8(sizeof(PgHdr)),
pCache->bPurgeable
);
if( pNew==0 ) return SQLITE_NOMEM_BKPT;
sqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache));
if( pCache->pCache ){
sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache);
}
pCache->pCache = pNew;
pCache->szPage = szPage;
pcacheTrace(("%p.PAGESIZE %d\n",pCache,szPage));
}
return SQLITE_OK;
}
sqlite3_pcache_page *sqlite3PcacheFetch(
PCache *pCache,
Pgno pgno,
int createFlag
){
int eCreate;
sqlite3_pcache_page *pRes;
assert( pCache!=0 );
assert( pCache->pCache!=0 );
assert( createFlag==3 || createFlag==0 );
assert( pCache->eCreate==((pCache->bPurgeable && pCache->pDirty) ? 1 : 2) );
eCreate = createFlag & pCache->eCreate;
assert( eCreate==0 || eCreate==1 || eCreate==2 );
assert( createFlag==0 || pCache->eCreate==eCreate );
assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) );
pRes = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate);
pcacheTrace(("%p.FETCH %d%s (result: %p) ",pCache,pgno,
createFlag?" create":"",pRes));
pcachePageTrace(pgno, pRes);
return pRes;
}
int sqlite3PcacheFetchStress(
PCache *pCache,
Pgno pgno,
sqlite3_pcache_page **ppPage
){
PgHdr *pPg;
if( pCache->eCreate==2 ) return 0;
if( sqlite3PcachePagecount(pCache)>pCache->szSpill ){
for(pPg=pCache->pSynced;
pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC));
pPg=pPg->pDirtyPrev
);
pCache->pSynced = pPg;
if( !pPg ){
for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev);
}
if( pPg ){
int rc;
#ifdef SQLITE_LOG_CACHE_SPILL
sqlite3_log(SQLITE_FULL,
"spill page %d making room for %d - cache used: %d/%d",
pPg->pgno, pgno,
sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache),
numberOfCachePages(pCache));
#endif
pcacheTrace(("%p.SPILL %d\n",pCache,pPg->pgno));
rc = pCache->xStress(pCache->pStress, pPg);
pcacheDump(pCache);
if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
return rc;
}
}
}
*ppPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2);
return *ppPage==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK;
}
static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit(
PCache *pCache,
Pgno pgno,
sqlite3_pcache_page *pPage
){
PgHdr *pPgHdr;
assert( pPage!=0 );
pPgHdr = (PgHdr*)pPage->pExtra;
assert( pPgHdr->pPage==0 );
memset(&pPgHdr->pDirty, 0, sizeof(PgHdr) - offsetof(PgHdr,pDirty));
pPgHdr->pPage = pPage;
pPgHdr->pData = pPage->pBuf;
pPgHdr->pExtra = (void *)&pPgHdr[1];
memset(pPgHdr->pExtra, 0, 8);
pPgHdr->pCache = pCache;
pPgHdr->pgno = pgno;
pPgHdr->flags = PGHDR_CLEAN;
return sqlite3PcacheFetchFinish(pCache,pgno,pPage);
}
PgHdr *sqlite3PcacheFetchFinish(
PCache *pCache,
Pgno pgno,
sqlite3_pcache_page *pPage
){
PgHdr *pPgHdr;
assert( pPage!=0 );
pPgHdr = (PgHdr *)pPage->pExtra;
if( !pPgHdr->pPage ){
return pcacheFetchFinishWithInit(pCache, pgno, pPage);
}
pCache->nRefSum++;
pPgHdr->nRef++;
assert( sqlite3PcachePageSanity(pPgHdr) );
return pPgHdr;
}
void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){
assert( p->nRef>0 );
p->pCache->nRefSum--;
if( (--p->nRef)==0 ){
if( p->flags&PGHDR_CLEAN ){
pcacheUnpin(p);
}else{
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT);
assert( sqlite3PcachePageSanity(p) );
}
}
}
void sqlite3PcacheRef(PgHdr *p){
assert(p->nRef>0);
assert( sqlite3PcachePageSanity(p) );
p->nRef++;
p->pCache->nRefSum++;
}
void sqlite3PcacheDrop(PgHdr *p){
assert( p->nRef==1 );
assert( sqlite3PcachePageSanity(p) );
if( p->flags&PGHDR_DIRTY ){
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE);
}
p->pCache->nRefSum--;
sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 1);
}
void sqlite3PcacheMakeDirty(PgHdr *p){
assert( p->nRef>0 );
assert( sqlite3PcachePageSanity(p) );
if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){
p->flags &= ~PGHDR_DONT_WRITE;
if( p->flags & PGHDR_CLEAN ){
p->flags ^= (PGHDR_DIRTY|PGHDR_CLEAN);
pcacheTrace(("%p.DIRTY %d\n",p->pCache,p->pgno));
assert( (p->flags & (PGHDR_DIRTY|PGHDR_CLEAN))==PGHDR_DIRTY );
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD);
assert( sqlite3PcachePageSanity(p) );
}
assert( sqlite3PcachePageSanity(p) );
}
}
void sqlite3PcacheMakeClean(PgHdr *p){
assert( sqlite3PcachePageSanity(p) );
assert( (p->flags & PGHDR_DIRTY)!=0 );
assert( (p->flags & PGHDR_CLEAN)==0 );
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE);
p->flags &= ~(PGHDR_DIRTY|PGHDR_NEED_SYNC|PGHDR_WRITEABLE);
p->flags |= PGHDR_CLEAN;
pcacheTrace(("%p.CLEAN %d\n",p->pCache,p->pgno));
assert( sqlite3PcachePageSanity(p) );
if( p->nRef==0 ){
pcacheUnpin(p);
}
}
void sqlite3PcacheCleanAll(PCache *pCache){
PgHdr *p;
pcacheTrace(("%p.CLEAN-ALL\n",pCache));
while( (p = pCache->pDirty)!=0 ){
sqlite3PcacheMakeClean(p);
}
}
void sqlite3PcacheClearWritable(PCache *pCache){
PgHdr *p;
pcacheTrace(("%p.CLEAR-WRITEABLE\n",pCache));
for(p=pCache->pDirty; p; p=p->pDirtyNext){
p->flags &= ~(PGHDR_NEED_SYNC|PGHDR_WRITEABLE);
}
pCache->pSynced = pCache->pDirtyTail;
}
void sqlite3PcacheClearSyncFlags(PCache *pCache){
PgHdr *p;
for(p=pCache->pDirty; p; p=p->pDirtyNext){
p->flags &= ~PGHDR_NEED_SYNC;
}
pCache->pSynced = pCache->pDirtyTail;
}
void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){
PCache *pCache = p->pCache;
sqlite3_pcache_page *pOther;
assert( p->nRef>0 );
assert( newPgno>0 );
assert( sqlite3PcachePageSanity(p) );
pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno));
pOther = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, newPgno, 0);
if( pOther ){
PgHdr *pXPage = (PgHdr*)pOther->pExtra;
assert( pXPage->nRef==0 );
pXPage->nRef++;
pCache->nRefSum++;
sqlite3PcacheDrop(pXPage);
}
sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno);
p->pgno = newPgno;
if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT);
assert( sqlite3PcachePageSanity(p) );
}
}
void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
if( pCache->pCache ){
PgHdr *p;
PgHdr *pNext;
pcacheTrace(("%p.TRUNCATE %d\n",pCache,pgno));
for(p=pCache->pDirty; p; p=pNext){
pNext = p->pDirtyNext;
assert( p->pgno>0 );
if( p->pgno>pgno ){
assert( p->flags&PGHDR_DIRTY );
sqlite3PcacheMakeClean(p);
}
}
if( pgno==0 && pCache->nRefSum ){
sqlite3_pcache_page *pPage1;
pPage1 = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache,1,0);
if( ALWAYS(pPage1) ){
memset(pPage1->pBuf, 0, pCache->szPage);
pgno = 1;
}
}
sqlite3GlobalConfig.pcache2.xTruncate(pCache->pCache, pgno+1);
}
}
void sqlite3PcacheClose(PCache *pCache){
assert( pCache->pCache!=0 );
pcacheTrace(("%p.CLOSE\n",pCache));
sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache);
}
void sqlite3PcacheClear(PCache *pCache){
sqlite3PcacheTruncate(pCache, 0);
}
static PgHdr *pcacheMergeDirtyList(PgHdr *pA, PgHdr *pB){
PgHdr result, *pTail;
pTail = &result;
assert( pA!=0 && pB!=0 );
for(;;){
if( pA->pgno<pB->pgno ){
pTail->pDirty = pA;
pTail = pA;
pA = pA->pDirty;
if( pA==0 ){
pTail->pDirty = pB;
break;
}
}else{
pTail->pDirty = pB;
pTail = pB;
pB = pB->pDirty;
if( pB==0 ){
pTail->pDirty = pA;
break;
}
}
}
return result.pDirty;
}
#define N_SORT_BUCKET 32
static PgHdr *pcacheSortDirtyList(PgHdr *pIn){
PgHdr *a[N_SORT_BUCKET], *p;
int i;
memset(a, 0, sizeof(a));
while( pIn ){
p = pIn;
pIn = p->pDirty;
p->pDirty = 0;
for(i=0; ALWAYS(i<N_SORT_BUCKET-1); i++){
if( a[i]==0 ){
a[i] = p;
break;
}else{
p = pcacheMergeDirtyList(a[i], p);
a[i] = 0;
}
}
if( NEVER(i==N_SORT_BUCKET-1) ){
a[i] = pcacheMergeDirtyList(a[i], p);
}
}
p = a[0];
for(i=1; i<N_SORT_BUCKET; i++){
if( a[i]==0 ) continue;
p = p ? pcacheMergeDirtyList(p, a[i]) : a[i];
}
return p;
}
PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
PgHdr *p;
for(p=pCache->pDirty; p; p=p->pDirtyNext){
p->pDirty = p->pDirtyNext;
}
return pcacheSortDirtyList(pCache->pDirty);
}
i64 sqlite3PcacheRefCount(PCache *pCache){
return pCache->nRefSum;
}
i64 sqlite3PcachePageRefcount(PgHdr *p){
return p->nRef;
}
int sqlite3PcachePagecount(PCache *pCache){
assert( pCache->pCache!=0 );
return sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache);
}
#ifdef SQLITE_TEST
int sqlite3PcacheGetCachesize(PCache *pCache){
return numberOfCachePages(pCache);
}
#endif
void sqlite3PcacheSetCachesize(PCache *pCache, int mxPage){
assert( pCache->pCache!=0 );
pCache->szCache = mxPage;
sqlite3GlobalConfig.pcache2.xCachesize(pCache->pCache,
numberOfCachePages(pCache));
}
int sqlite3PcacheSetSpillsize(PCache *p, int mxPage){
int res;
assert( p->pCache!=0 );
if( mxPage ){
if( mxPage<0 ){
mxPage = (int)((-1024*(i64)mxPage)/(p->szPage+p->szExtra));
}
p->szSpill = mxPage;
}
res = numberOfCachePages(p);
if( res<p->szSpill ) res = p->szSpill;
return res;
}
void sqlite3PcacheShrink(PCache *pCache){
assert( pCache->pCache!=0 );
sqlite3GlobalConfig.pcache2.xShrink(pCache->pCache);
}
int sqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); }
int sqlite3PCachePercentDirty(PCache *pCache){
PgHdr *pDirty;
int nDirty = 0;
int nCache = numberOfCachePages(pCache);
for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext) nDirty++;
return nCache ? (int)(((i64)nDirty * 100) / nCache) : 0;
}
#ifdef SQLITE_DIRECT_OVERFLOW_READ
int sqlite3PCacheIsDirty(PCache *pCache){
return (pCache->pDirty!=0);
}
#endif
#if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG)
void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)){
PgHdr *pDirty;
for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext){
xIter(pDirty);
}
}
#endif