Index: third_party/sqlite/src/src/pcache1.c |
diff --git a/third_party/sqlite/src/src/pcache1.c b/third_party/sqlite/src/src/pcache1.c |
index 036af54a23246ee0190a693b520cdf99dfdcd2aa..ad443954bffd0901fcc3e0e93a3b0788d7b997c4 100644 |
--- a/third_party/sqlite/src/src/pcache1.c |
+++ b/third_party/sqlite/src/src/pcache1.c |
@@ -15,8 +15,6 @@ |
** 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.19 2009/07/17 11:44:07 drh Exp $ |
*/ |
#include "sqliteInt.h" |
@@ -24,24 +22,62 @@ |
typedef struct PCache1 PCache1; |
typedef struct PgHdr1 PgHdr1; |
typedef struct PgFreeslot PgFreeslot; |
+typedef struct PGroup PGroup; |
+ |
+/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set |
+** of one or more PCaches that are able to recycle each others unpinned |
+** pages when they are under memory pressure. A PGroup is an instance of |
+** the following object. |
+** |
+** This page cache implementation works in one of two modes: |
+** |
+** (1) Every PCache is the sole member of its own PGroup. There is |
+** one PGroup per PCache. |
+** |
+** (2) There is a single global PGroup that all PCaches are a member |
+** of. |
+** |
+** Mode 1 uses more memory (since PCache instances are not able to rob |
+** unused pages from other PCaches) but it also operates without a mutex, |
+** and is therefore often faster. Mode 2 requires a mutex in order to be |
+** threadsafe, but is able recycle pages more efficient. |
+** |
+** For mode (1), PGroup.mutex is NULL. For mode (2) there is only a single |
+** PGroup which is the pcache1.grp global variable and its mutex is |
+** SQLITE_MUTEX_STATIC_LRU. |
+*/ |
+struct PGroup { |
+ sqlite3_mutex *mutex; /* MUTEX_STATIC_LRU or NULL */ |
+ int nMaxPage; /* Sum of nMax for purgeable caches */ |
+ int nMinPage; /* Sum of nMin for purgeable caches */ |
+ int mxPinned; /* nMaxpage + 10 - nMinPage */ |
+ int nCurrentPage; /* Number of purgeable pages allocated */ |
+ PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */ |
+}; |
-/* Pointers to structures of this type are cast and returned as |
-** opaque sqlite3_pcache* handles |
+/* Each page cache is an instance of the following object. Every |
+** open database file (including each in-memory database and each |
+** temporary or transient database) has a single page cache which |
+** is an instance of this object. |
+** |
+** 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. |
+ ** The PGroup mutex must be held when accessing nMax. |
*/ |
+ PGroup *pGroup; /* PGroup this cache belongs to */ |
int szPage; /* Size of allocated pages in bytes */ |
int bPurgeable; /* True if cache is purgeable */ |
unsigned int nMin; /* Minimum number of pages reserved */ |
unsigned int nMax; /* Configured "cache_size" value */ |
+ unsigned int n90pct; /* nMax*9/10 */ |
/* Hash table of all pages. The following variables may only be accessed |
- ** when the accessor is holding the global mutex (see pcache1EnterMutex() |
- ** and pcache1LeaveMutex()). |
+ ** when the accessor is holding the PGroup mutex. |
*/ |
unsigned int nRecyclable; /* Number of pages in the LRU list */ |
unsigned int nPage; /* Total number of pages in apHash */ |
@@ -77,18 +113,27 @@ struct PgFreeslot { |
** 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 */ |
- int isInit; /* True if initialized */ |
+ PGroup grp; /* The global PGroup for mode (2) */ |
+ |
+ /* Variables related to SQLITE_CONFIG_PAGECACHE settings. The |
+ ** szSlot, nSlot, pStart, pEnd, nReserve, and isInit values are all |
+ ** fixed at sqlite3_initialize() time and do not require mutex protection. |
+ ** The nFreeSlot and pFree values do require mutex protection. |
+ */ |
+ int isInit; /* True if initialized */ |
+ int szSlot; /* Size of each free slot */ |
+ int nSlot; /* The number of pcache slots */ |
+ int nReserve; /* Try to keep nFreeSlot above this */ |
+ void *pStart, *pEnd; /* Bounds of pagecache malloc range */ |
+ /* Above requires no mutex. Use mutex below for variable that follow. */ |
+ sqlite3_mutex *mutex; /* Mutex for accessing the following: */ |
+ int nFreeSlot; /* Number of unused pcache slots */ |
+ PgFreeslot *pFree; /* Free page blocks */ |
+ /* The following value requires a mutex to change. We skip the mutex on |
+ ** reading because (1) most platforms read a 32-bit integer atomically and |
+ ** (2) even if an incorrect value is read, no great harm is done since this |
+ ** is really just an optimization. */ |
+ int bUnderPressure; /* True if low on PAGECACHE memory */ |
} pcache1_g; |
/* |
@@ -114,10 +159,10 @@ static SQLITE_WSD struct PCacheGlobal { |
#define PAGE_TO_PGHDR1(c, p) (PgHdr1*)(((char*)p) + c->szPage) |
/* |
-** Macros to enter and leave the global LRU mutex. |
+** Macros to enter and leave the PCache LRU mutex. |
*/ |
-#define pcache1EnterMutex() sqlite3_mutex_enter(pcache1.mutex) |
-#define pcache1LeaveMutex() sqlite3_mutex_leave(pcache1.mutex) |
+#define pcache1EnterMutex(X) sqlite3_mutex_enter((X)->mutex) |
+#define pcache1LeaveMutex(X) sqlite3_mutex_leave((X)->mutex) |
/******************************************************************************/ |
/******** Page Allocation/SQLITE_CONFIG_PCACHE Related Functions **************/ |
@@ -127,14 +172,20 @@ static SQLITE_WSD struct PCacheGlobal { |
** 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. |
+** |
+** This routine is called from sqlite3_initialize() and so it is guaranteed |
+** to be serialized already. There is no need for further mutexing. |
*/ |
void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){ |
if( pcache1.isInit ){ |
PgFreeslot *p; |
sz = ROUNDDOWN8(sz); |
pcache1.szSlot = sz; |
+ pcache1.nSlot = pcache1.nFreeSlot = n; |
+ pcache1.nReserve = n>90 ? 10 : (n/10 + 1); |
pcache1.pStart = pBuf; |
pcache1.pFree = 0; |
+ pcache1.bUnderPressure = 0; |
while( n-- ){ |
p = (PgFreeslot*)pBuf; |
p->pNext = pcache1.pFree; |
@@ -150,31 +201,38 @@ void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){ |
** 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(). |
+** |
+** Multiple threads can run this routine at the same time. Global variables |
+** in pcache1 need to be protected via mutex. |
*/ |
static void *pcache1Alloc(int nByte){ |
- void *p; |
- assert( sqlite3_mutex_held(pcache1.mutex) ); |
- if( nByte<=pcache1.szSlot && pcache1.pFree ){ |
- assert( pcache1.isInit ); |
+ void *p = 0; |
+ assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); |
+ sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); |
+ if( nByte<=pcache1.szSlot ){ |
+ sqlite3_mutex_enter(pcache1.mutex); |
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. |
+ if( p ){ |
+ pcache1.pFree = pcache1.pFree->pNext; |
+ pcache1.nFreeSlot--; |
+ pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve; |
+ assert( pcache1.nFreeSlot>=0 ); |
+ sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1); |
+ } |
+ sqlite3_mutex_leave(pcache1.mutex); |
+ } |
+ if( p==0 ){ |
+ /* Memory is not available in the SQLITE_CONFIG_PAGECACHE pool. Get |
+ ** it from sqlite3Malloc instead. |
*/ |
- pcache1LeaveMutex(); |
p = sqlite3Malloc(nByte); |
- pcache1EnterMutex(); |
if( p ){ |
int sz = sqlite3MallocSize(p); |
+ sqlite3_mutex_enter(pcache1.mutex); |
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); |
+ sqlite3_mutex_leave(pcache1.mutex); |
} |
+ sqlite3MemdebugSetType(p, MEMTYPE_PCACHE); |
} |
return p; |
} |
@@ -183,21 +241,48 @@ static void *pcache1Alloc(int nByte){ |
** 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; |
+ sqlite3_mutex_enter(pcache1.mutex); |
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, -1); |
pSlot = (PgFreeslot*)p; |
pSlot->pNext = pcache1.pFree; |
pcache1.pFree = pSlot; |
+ pcache1.nFreeSlot++; |
+ pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve; |
+ assert( pcache1.nFreeSlot<=pcache1.nSlot ); |
+ sqlite3_mutex_leave(pcache1.mutex); |
}else{ |
- int iSize = sqlite3MallocSize(p); |
+ int iSize; |
+ assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) ); |
+ sqlite3MemdebugSetType(p, MEMTYPE_HEAP); |
+ iSize = sqlite3MallocSize(p); |
+ sqlite3_mutex_enter(pcache1.mutex); |
sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -iSize); |
+ sqlite3_mutex_leave(pcache1.mutex); |
sqlite3_free(p); |
} |
} |
+#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT |
+/* |
+** Return the size of a pcache allocation |
+*/ |
+static int pcache1MemSize(void *p){ |
+ if( p>=pcache1.pStart && p<pcache1.pEnd ){ |
+ return pcache1.szSlot; |
+ }else{ |
+ int iSize; |
+ assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) ); |
+ sqlite3MemdebugSetType(p, MEMTYPE_HEAP); |
+ iSize = sqlite3MallocSize(p); |
+ sqlite3MemdebugSetType(p, MEMTYPE_PCACHE); |
+ return iSize; |
+ } |
+} |
+#endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */ |
+ |
/* |
** Allocate a new page object initially associated with cache pCache. |
*/ |
@@ -208,7 +293,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){ |
if( pPg ){ |
p = PAGE_TO_PGHDR1(pCache, pPg); |
if( pCache->bPurgeable ){ |
- pcache1.nCurrentPage++; |
+ pCache->pGroup->nCurrentPage++; |
} |
}else{ |
p = 0; |
@@ -225,8 +310,9 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){ |
*/ |
static void pcache1FreePage(PgHdr1 *p){ |
if( ALWAYS(p) ){ |
- if( p->pCache->bPurgeable ){ |
- pcache1.nCurrentPage--; |
+ PCache1 *pCache = p->pCache; |
+ if( pCache->bPurgeable ){ |
+ pCache->pGroup->nCurrentPage--; |
} |
pcache1Free(PGHDR1_TO_PAGE(p)); |
} |
@@ -238,20 +324,39 @@ static void pcache1FreePage(PgHdr1 *p){ |
** exists, this function falls back to sqlite3Malloc(). |
*/ |
void *sqlite3PageMalloc(int sz){ |
- void *p; |
- pcache1EnterMutex(); |
- p = pcache1Alloc(sz); |
- pcache1LeaveMutex(); |
- return p; |
+ return pcache1Alloc(sz); |
} |
/* |
** Free an allocated buffer obtained from sqlite3PageMalloc(). |
*/ |
void sqlite3PageFree(void *p){ |
- pcache1EnterMutex(); |
pcache1Free(p); |
- pcache1LeaveMutex(); |
+} |
+ |
+ |
+/* |
+** Return true if it desirable to avoid allocating a new page cache |
+** entry. |
+** |
+** If memory was allocated specifically to the page cache using |
+** SQLITE_CONFIG_PAGECACHE but that memory has all been used, then |
+** it is desirable to avoid allocating a new page cache entry because |
+** presumably SQLITE_CONFIG_PAGECACHE was suppose to be sufficient |
+** for all page cache needs and we should not need to spill the |
+** allocation onto the heap. |
+** |
+** Or, the heap is used for all page cache memory put the heap is |
+** under memory pressure, then again it is desirable to avoid |
+** allocating a new page cache entry in order to avoid stressing |
+** the heap even further. |
+*/ |
+static int pcache1UnderMemoryPressure(PCache1 *pCache){ |
+ if( pcache1.nSlot && pCache->szPage<=pcache1.szSlot ){ |
+ return pcache1.bUnderPressure; |
+ }else{ |
+ return sqlite3HeapNearlyFull(); |
+ } |
} |
/******************************************************************************/ |
@@ -261,25 +366,25 @@ void sqlite3PageFree(void *p){ |
** 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. |
+** The PCache mutex must be held when this function is called. |
*/ |
static int pcache1ResizeHash(PCache1 *p){ |
PgHdr1 **apNew; |
unsigned int nNew; |
unsigned int i; |
- assert( sqlite3_mutex_held(pcache1.mutex) ); |
+ assert( sqlite3_mutex_held(p->pGroup->mutex) ); |
nNew = p->nHash*2; |
if( nNew<256 ){ |
nNew = 256; |
} |
- pcache1LeaveMutex(); |
+ pcache1LeaveMutex(p->pGroup); |
if( p->nHash ){ sqlite3BeginBenignMalloc(); } |
apNew = (PgHdr1 **)sqlite3_malloc(sizeof(PgHdr1 *)*nNew); |
if( p->nHash ){ sqlite3EndBenignMalloc(); } |
- pcache1EnterMutex(); |
+ pcache1EnterMutex(p->pGroup); |
if( apNew ){ |
memset(apNew, 0, sizeof(PgHdr1 *)*nNew); |
for(i=0; i<p->nHash; i++){ |
@@ -302,25 +407,33 @@ static int pcache1ResizeHash(PCache1 *p){ |
/* |
** 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 |
+** PGroup LRU list, if is part of it. If pPage is not part of the PGroup |
** LRU list, then this function is a no-op. |
** |
-** The global mutex must be held when this function is called. |
+** The PGroup mutex must be held when this function is called. |
+** |
+** If pPage is NULL then this routine is a no-op. |
*/ |
static void pcache1PinPage(PgHdr1 *pPage){ |
- assert( sqlite3_mutex_held(pcache1.mutex) ); |
- if( pPage && (pPage->pLruNext || pPage==pcache1.pLruTail) ){ |
+ PCache1 *pCache; |
+ PGroup *pGroup; |
+ |
+ if( pPage==0 ) return; |
+ pCache = pPage->pCache; |
+ pGroup = pCache->pGroup; |
+ assert( sqlite3_mutex_held(pGroup->mutex) ); |
+ if( pPage->pLruNext || pPage==pGroup->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( pGroup->pLruHead==pPage ){ |
+ pGroup->pLruHead = pPage->pLruNext; |
} |
- if( pcache1.pLruTail==pPage ){ |
- pcache1.pLruTail = pPage->pLruPrev; |
+ if( pGroup->pLruTail==pPage ){ |
+ pGroup->pLruTail = pPage->pLruPrev; |
} |
pPage->pLruNext = 0; |
pPage->pLruPrev = 0; |
@@ -333,13 +446,14 @@ static void pcache1PinPage(PgHdr1 *pPage){ |
** 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. |
+** The PGroup mutex must be held when this function is called. |
*/ |
static void pcache1RemoveFromHash(PgHdr1 *pPage){ |
unsigned int h; |
PCache1 *pCache = pPage->pCache; |
PgHdr1 **pp; |
+ assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); |
h = pPage->iKey % pCache->nHash; |
for(pp=&pCache->apHash[h]; (*pp)!=pPage; pp=&(*pp)->pNext); |
*pp = (*pp)->pNext; |
@@ -348,13 +462,14 @@ static void pcache1RemoveFromHash(PgHdr1 *pPage){ |
} |
/* |
-** If there are currently more than pcache.nMaxPage pages allocated, try |
-** to recycle pages to reduce the number allocated to pcache.nMaxPage. |
+** If there are currently more than nMaxPage pages allocated, try |
+** to recycle pages to reduce the number allocated to nMaxPage. |
*/ |
-static void pcache1EnforceMaxPage(void){ |
- assert( sqlite3_mutex_held(pcache1.mutex) ); |
- while( pcache1.nCurrentPage>pcache1.nMaxPage && pcache1.pLruTail ){ |
- PgHdr1 *p = pcache1.pLruTail; |
+static void pcache1EnforceMaxPage(PGroup *pGroup){ |
+ assert( sqlite3_mutex_held(pGroup->mutex) ); |
+ while( pGroup->nCurrentPage>pGroup->nMaxPage && pGroup->pLruTail ){ |
+ PgHdr1 *p = pGroup->pLruTail; |
+ assert( p->pCache->pGroup==pGroup ); |
pcache1PinPage(p); |
pcache1RemoveFromHash(p); |
pcache1FreePage(p); |
@@ -366,15 +481,15 @@ static void pcache1EnforceMaxPage(void){ |
** 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. |
+** The PCache mutex must be held when this function is called. |
*/ |
static void pcache1TruncateUnsafe( |
- PCache1 *pCache, |
- unsigned int iLimit |
+ PCache1 *pCache, /* The cache to truncate */ |
+ unsigned int iLimit /* Drop pages with this pgno or larger */ |
){ |
- TESTONLY( unsigned int nPage = 0; ) /* Used to assert pCache->nPage is correct */ |
+ TESTONLY( unsigned int nPage = 0; ) /* To assert pCache->nPage is correct */ |
unsigned int h; |
- assert( sqlite3_mutex_held(pcache1.mutex) ); |
+ assert( sqlite3_mutex_held(pCache->pGroup->mutex) ); |
for(h=0; h<pCache->nHash; h++){ |
PgHdr1 **pp = &pCache->apHash[h]; |
PgHdr1 *pPage; |
@@ -404,8 +519,10 @@ static int pcache1Init(void *NotUsed){ |
assert( pcache1.isInit==0 ); |
memset(&pcache1, 0, sizeof(pcache1)); |
if( sqlite3GlobalConfig.bCoreMutex ){ |
- pcache1.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU); |
+ pcache1.grp.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_LRU); |
+ pcache1.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_PMEM); |
} |
+ pcache1.grp.mxPinned = 10; |
pcache1.isInit = 1; |
return SQLITE_OK; |
} |
@@ -427,18 +544,47 @@ static void pcache1Shutdown(void *NotUsed){ |
** Allocate a new cache. |
*/ |
static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){ |
- PCache1 *pCache; |
+ PCache1 *pCache; /* The newly created page cache */ |
+ PGroup *pGroup; /* The group the new page cache will belong to */ |
+ int sz; /* Bytes of memory required to allocate the new cache */ |
+ |
+ /* |
+ ** The seperateCache variable is true if each PCache has its own private |
+ ** PGroup. In other words, separateCache is true for mode (1) where no |
+ ** mutexing is required. |
+ ** |
+ ** * Always use a unified cache (mode-2) if ENABLE_MEMORY_MANAGEMENT |
+ ** |
+ ** * Always use a unified cache in single-threaded applications |
+ ** |
+ ** * Otherwise (if multi-threaded and ENABLE_MEMORY_MANAGEMENT is off) |
+ ** use separate caches (mode-1) |
+ */ |
+#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT) || SQLITE_THREADSAFE==0 |
+ const int separateCache = 0; |
+#else |
+ int separateCache = sqlite3GlobalConfig.bCoreMutex>0; |
+#endif |
- pCache = (PCache1 *)sqlite3_malloc(sizeof(PCache1)); |
+ sz = sizeof(PCache1) + sizeof(PGroup)*separateCache; |
+ pCache = (PCache1 *)sqlite3_malloc(sz); |
if( pCache ){ |
- memset(pCache, 0, sizeof(PCache1)); |
+ memset(pCache, 0, sz); |
+ if( separateCache ){ |
+ pGroup = (PGroup*)&pCache[1]; |
+ pGroup->mxPinned = 10; |
+ }else{ |
+ pGroup = &pcache1_g.grp; |
+ } |
+ pCache->pGroup = pGroup; |
pCache->szPage = szPage; |
pCache->bPurgeable = (bPurgeable ? 1 : 0); |
if( bPurgeable ){ |
pCache->nMin = 10; |
- pcache1EnterMutex(); |
- pcache1.nMinPage += pCache->nMin; |
- pcache1LeaveMutex(); |
+ pcache1EnterMutex(pGroup); |
+ pGroup->nMinPage += pCache->nMin; |
+ pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; |
+ pcache1LeaveMutex(pGroup); |
} |
} |
return (sqlite3_pcache *)pCache; |
@@ -452,11 +598,14 @@ static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){ |
static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ |
PCache1 *pCache = (PCache1 *)p; |
if( pCache->bPurgeable ){ |
- pcache1EnterMutex(); |
- pcache1.nMaxPage += (nMax - pCache->nMax); |
+ PGroup *pGroup = pCache->pGroup; |
+ pcache1EnterMutex(pGroup); |
+ pGroup->nMaxPage += (nMax - pCache->nMax); |
+ pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; |
pCache->nMax = nMax; |
- pcache1EnforceMaxPage(); |
- pcache1LeaveMutex(); |
+ pCache->n90pct = pCache->nMax*9/10; |
+ pcache1EnforceMaxPage(pGroup); |
+ pcache1LeaveMutex(pGroup); |
} |
} |
@@ -465,9 +614,10 @@ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ |
*/ |
static int pcache1Pagecount(sqlite3_pcache *p){ |
int n; |
- pcache1EnterMutex(); |
- n = ((PCache1 *)p)->nPage; |
- pcache1LeaveMutex(); |
+ PCache1 *pCache = (PCache1*)p; |
+ pcache1EnterMutex(pCache->pGroup); |
+ n = pCache->nPage; |
+ pcache1LeaveMutex(pCache->pGroup); |
return n; |
} |
@@ -495,14 +645,16 @@ static int pcache1Pagecount(sqlite3_pcache *p){ |
** 2. If createFlag==0 and the page is not already in the cache, NULL is |
** returned. |
** |
-** 3. If createFlag is 1, and the page is not already in the cache, |
-** and if either of the following are true, return NULL: |
+** 3. If createFlag is 1, and the page is not already in the cache, then |
+** return NULL (do not allocate a new page) if any of the following |
+** conditions are true: |
** |
** (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. |
+** nMin for all other purgeable caches, or |
** |
** 4. If none of the first three conditions apply and the cache is marked |
** as purgeable, and if one of the following is true: |
@@ -514,6 +666,9 @@ static int pcache1Pagecount(sqlite3_pcache *p){ |
** already equal to or greater than the sum of nMax for all |
** purgeable caches, |
** |
+** (c) The system is under memory pressure and wants to avoid |
+** unnecessary pages cache entry allocations |
+** |
** 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. |
@@ -521,30 +676,50 @@ static int pcache1Pagecount(sqlite3_pcache *p){ |
** 5. Otherwise, allocate and return a new page buffer. |
*/ |
static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){ |
- unsigned int nPinned; |
+ int nPinned; |
PCache1 *pCache = (PCache1 *)p; |
+ PGroup *pGroup; |
PgHdr1 *pPage = 0; |
assert( pCache->bPurgeable || createFlag!=1 ); |
- pcache1EnterMutex(); |
- if( createFlag==1 ) sqlite3BeginBenignMalloc(); |
+ assert( pCache->bPurgeable || pCache->nMin==0 ); |
+ assert( pCache->bPurgeable==0 || pCache->nMin==10 ); |
+ assert( pCache->nMin==0 || pCache->bPurgeable ); |
+ pcache1EnterMutex(pGroup = pCache->pGroup); |
- /* Search the hash table for an existing entry. */ |
+ /* Step 1: 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); |
} |
+ /* Step 2: Abort if no existing page is found and createFlag is 0 */ |
if( pPage || createFlag==0 ){ |
pcache1PinPage(pPage); |
goto fetch_out; |
} |
- /* Step 3 of header comment. */ |
+ /* The pGroup local variable will normally be initialized by the |
+ ** pcache1EnterMutex() macro above. But if SQLITE_MUTEX_OMIT is defined, |
+ ** then pcache1EnterMutex() is a no-op, so we have to initialize the |
+ ** local variable here. Delaying the initialization of pGroup is an |
+ ** optimization: The common case is to exit the module before reaching |
+ ** this point. |
+ */ |
+#ifdef SQLITE_MUTEX_OMIT |
+ pGroup = pCache->pGroup; |
+#endif |
+ |
+ |
+ /* Step 3: Abort if createFlag is 1 but the cache is nearly full */ |
nPinned = pCache->nPage - pCache->nRecyclable; |
+ assert( nPinned>=0 ); |
+ assert( pGroup->mxPinned == pGroup->nMaxPage + 10 - pGroup->nMinPage ); |
+ assert( pCache->n90pct == pCache->nMax*9/10 ); |
if( createFlag==1 && ( |
- nPinned>=(pcache1.nMaxPage+pCache->nMin-pcache1.nMinPage) |
- || nPinned>=(pCache->nMax * 9 / 10) |
+ nPinned>=pGroup->mxPinned |
+ || nPinned>=(int)pCache->n90pct |
+ || pcache1UnderMemoryPressure(pCache) |
)){ |
goto fetch_out; |
} |
@@ -553,18 +728,22 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){ |
goto fetch_out; |
} |
- /* Step 4. Try to recycle a page buffer if appropriate. */ |
- if( pCache->bPurgeable && pcache1.pLruTail && ( |
- (pCache->nPage+1>=pCache->nMax) || pcache1.nCurrentPage>=pcache1.nMaxPage |
+ /* Step 4. Try to recycle a page. */ |
+ if( pCache->bPurgeable && pGroup->pLruTail && ( |
+ (pCache->nPage+1>=pCache->nMax) |
+ || pGroup->nCurrentPage>=pGroup->nMaxPage |
+ || pcache1UnderMemoryPressure(pCache) |
)){ |
- pPage = pcache1.pLruTail; |
+ PCache1 *pOtherCache; |
+ pPage = pGroup->pLruTail; |
pcache1RemoveFromHash(pPage); |
pcache1PinPage(pPage); |
- if( pPage->pCache->szPage!=pCache->szPage ){ |
+ if( (pOtherCache = pPage->pCache)->szPage!=pCache->szPage ){ |
pcache1FreePage(pPage); |
pPage = 0; |
}else{ |
- pcache1.nCurrentPage -= (pPage->pCache->bPurgeable - pCache->bPurgeable); |
+ pGroup->nCurrentPage -= |
+ (pOtherCache->bPurgeable - pCache->bPurgeable); |
} |
} |
@@ -572,7 +751,11 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){ |
** attempt to allocate a new one. |
*/ |
if( !pPage ){ |
+ if( createFlag==1 ) sqlite3BeginBenignMalloc(); |
+ pcache1LeaveMutex(pGroup); |
pPage = pcache1AllocPage(pCache); |
+ pcache1EnterMutex(pGroup); |
+ if( createFlag==1 ) sqlite3EndBenignMalloc(); |
} |
if( pPage ){ |
@@ -591,8 +774,7 @@ fetch_out: |
if( pPage && iKey>pCache->iMaxKey ){ |
pCache->iMaxKey = iKey; |
} |
- if( createFlag==1 ) sqlite3EndBenignMalloc(); |
- pcache1LeaveMutex(); |
+ pcache1LeaveMutex(pGroup); |
return (pPage ? PGHDR1_TO_PAGE(pPage) : 0); |
} |
@@ -605,37 +787,34 @@ fetch_out: |
static void pcache1Unpin(sqlite3_pcache *p, void *pPg, int reuseUnlikely){ |
PCache1 *pCache = (PCache1 *)p; |
PgHdr1 *pPage = PAGE_TO_PGHDR1(pCache, pPg); |
+ PGroup *pGroup = pCache->pGroup; |
assert( pPage->pCache==pCache ); |
- pcache1EnterMutex(); |
+ pcache1EnterMutex(pGroup); |
/* It is an error to call this function if the page is already |
- ** part of the global LRU list. |
+ ** part of the PGroup LRU list. |
*/ |
assert( pPage->pLruPrev==0 && pPage->pLruNext==0 ); |
- assert( pcache1.pLruHead!=pPage && pcache1.pLruTail!=pPage ); |
+ assert( pGroup->pLruHead!=pPage && pGroup->pLruTail!=pPage ); |
- if( reuseUnlikely || pcache1.nCurrentPage>pcache1.nMaxPage ){ |
+ if( reuseUnlikely || pGroup->nCurrentPage>pGroup->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; |
+ /* Add the page to the PGroup LRU list. */ |
+ if( pGroup->pLruHead ){ |
+ pGroup->pLruHead->pLruPrev = pPage; |
+ pPage->pLruNext = pGroup->pLruHead; |
+ pGroup->pLruHead = pPage; |
}else{ |
- pcache1.pLruTail = pPage; |
- pcache1.pLruHead = pPage; |
+ pGroup->pLruTail = pPage; |
+ pGroup->pLruHead = pPage; |
} |
pCache->nRecyclable++; |
} |
- pcache1LeaveMutex(); |
+ pcache1LeaveMutex(pCache->pGroup); |
} |
/* |
@@ -654,7 +833,7 @@ static void pcache1Rekey( |
assert( pPage->iKey==iOld ); |
assert( pPage->pCache==pCache ); |
- pcache1EnterMutex(); |
+ pcache1EnterMutex(pCache->pGroup); |
h = iOld%pCache->nHash; |
pp = &pCache->apHash[h]; |
@@ -667,19 +846,11 @@ static void pcache1Rekey( |
pPage->iKey = iNew; |
pPage->pNext = pCache->apHash[h]; |
pCache->apHash[h] = pPage; |
- |
- /* The xRekey() interface is only used to move pages earlier in the |
- ** database file (in order to move all free pages to the end of the |
- ** file where they can be truncated off.) Hence, it is not possible |
- ** for the new page number to be greater than the largest previously |
- ** fetched page. But we retain the following test in case xRekey() |
- ** begins to be used in different ways in the future. |
- */ |
- if( NEVER(iNew>pCache->iMaxKey) ){ |
+ if( iNew>pCache->iMaxKey ){ |
pCache->iMaxKey = iNew; |
} |
- pcache1LeaveMutex(); |
+ pcache1LeaveMutex(pCache->pGroup); |
} |
/* |
@@ -691,12 +862,12 @@ static void pcache1Rekey( |
*/ |
static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){ |
PCache1 *pCache = (PCache1 *)p; |
- pcache1EnterMutex(); |
+ pcache1EnterMutex(pCache->pGroup); |
if( iLimit<=pCache->iMaxKey ){ |
pcache1TruncateUnsafe(pCache, iLimit); |
pCache->iMaxKey = iLimit-1; |
} |
- pcache1LeaveMutex(); |
+ pcache1LeaveMutex(pCache->pGroup); |
} |
/* |
@@ -706,12 +877,15 @@ static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){ |
*/ |
static void pcache1Destroy(sqlite3_pcache *p){ |
PCache1 *pCache = (PCache1 *)p; |
- pcache1EnterMutex(); |
+ PGroup *pGroup = pCache->pGroup; |
+ assert( pCache->bPurgeable || (pCache->nMax==0 && pCache->nMin==0) ); |
+ pcache1EnterMutex(pGroup); |
pcache1TruncateUnsafe(pCache, 0); |
- pcache1.nMaxPage -= pCache->nMax; |
- pcache1.nMinPage -= pCache->nMin; |
- pcache1EnforceMaxPage(); |
- pcache1LeaveMutex(); |
+ pGroup->nMaxPage -= pCache->nMax; |
+ pGroup->nMinPage -= pCache->nMin; |
+ pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; |
+ pcache1EnforceMaxPage(pGroup); |
+ pcache1LeaveMutex(pGroup); |
sqlite3_free(pCache->apHash); |
sqlite3_free(pCache); |
} |
@@ -722,7 +896,7 @@ static void pcache1Destroy(sqlite3_pcache *p){ |
** already provided an alternative. |
*/ |
void sqlite3PCacheSetDefault(void){ |
- static sqlite3_pcache_methods defaultMethods = { |
+ static const sqlite3_pcache_methods defaultMethods = { |
0, /* pArg */ |
pcache1Init, /* xInit */ |
pcache1Shutdown, /* xShutdown */ |
@@ -750,16 +924,18 @@ void sqlite3PCacheSetDefault(void){ |
*/ |
int sqlite3PcacheReleaseMemory(int nReq){ |
int nFree = 0; |
+ assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); |
+ assert( sqlite3_mutex_notheld(pcache1.mutex) ); |
if( pcache1.pStart==0 ){ |
PgHdr1 *p; |
- pcache1EnterMutex(); |
- while( (nReq<0 || nFree<nReq) && (p=pcache1.pLruTail) ){ |
- nFree += sqlite3MallocSize(PGHDR1_TO_PAGE(p)); |
+ pcache1EnterMutex(&pcache1.grp); |
+ while( (nReq<0 || nFree<nReq) && ((p=pcache1.grp.pLruTail)!=0) ){ |
+ nFree += pcache1MemSize(PGHDR1_TO_PAGE(p)); |
pcache1PinPage(p); |
pcache1RemoveFromHash(p); |
pcache1FreePage(p); |
} |
- pcache1LeaveMutex(); |
+ pcache1LeaveMutex(&pcache1.grp); |
} |
return nFree; |
} |
@@ -778,12 +954,12 @@ void sqlite3PcacheStats( |
){ |
PgHdr1 *p; |
int nRecyclable = 0; |
- for(p=pcache1.pLruHead; p; p=p->pLruNext){ |
+ for(p=pcache1.grp.pLruHead; p; p=p->pLruNext){ |
nRecyclable++; |
} |
- *pnCurrent = pcache1.nCurrentPage; |
- *pnMax = pcache1.nMaxPage; |
- *pnMin = pcache1.nMinPage; |
+ *pnCurrent = pcache1.grp.nCurrentPage; |
+ *pnMax = pcache1.grp.nMaxPage; |
+ *pnMin = pcache1.grp.nMinPage; |
*pnRecyclable = nRecyclable; |
} |
#endif |