using System.Diagnostics; using Pgno = System.UInt32; namespace Community.CsharpSqlite { using sqlite3_pcache = Sqlite3.PCache1; public partial class Sqlite3 { /* ** 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. ************************************************************************* ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart ** C#-SQLite is an independent reimplementation of the SQLite software library ** ** SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2 ** ************************************************************************* */ //#include "sqliteInt.h" //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. */ public class PGroup { public sqlite3_mutex mutex; /* MUTEX_STATIC_LRU or NULL */ public int nMaxPage; /* Sum of nMax for purgeable caches */ public int nMinPage; /* Sum of nMin for purgeable caches */ public int mxPinned; /* nMaxpage + 10 - nMinPage */ public int nCurrentPage; /* Number of purgeable pages allocated */ public PgHdr1 pLruHead, pLruTail; /* LRU list of unpinned pages */ // C# public PGroup() { mutex = new sqlite3_mutex(); } }; /* 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. */ public class 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 PGroup mutex must be held when accessing nMax. */ public PGroup pGroup; /* PGroup this cache belongs to */ public int szPage; /* Size of allocated pages in bytes */ public bool bPurgeable; /* True if cache is purgeable */ public int nMin; /* Minimum number of pages reserved */ public int nMax; /* Configured "cache_size" value */ public int n90pct; /* nMax*9/10 */ /* Hash table of all pages. The following variables may only be accessed ** when the accessor is holding the PGroup mutex. */ public int nRecyclable; /* Number of pages in the LRU list */ public int nPage; /* Total number of pages in apHash */ public int nHash; /* Number of slots in apHash[] */ public PgHdr1[] apHash; /* Hash table for fast lookup by key */ public Pgno iMaxKey; /* Largest key seen since xTruncate() */ public void Clear() { nRecyclable = 0; nPage = 0; nHash = 0; apHash = null; iMaxKey = 0; } }; /* ** Each cache entry is represented by an instance of the following ** structure. A buffer of PgHdr1.pCache.szPage bytes is allocated ** directly before this structure in memory (see the PGHDR1_TO_PAGE() ** macro below). */ public class PgHdr1 { public Pgno iKey; /* Key value (page number) */ public PgHdr1 pNext; /* Next in hash table chain */ public PCache1 pCache; /* Cache that currently owns this page */ public PgHdr1 pLruNext; /* Next in LRU list of unpinned pages */ public PgHdr1 pLruPrev; /* Previous in LRU list of unpinned pages */ // For C# public PgHdr pPgHdr = new PgHdr(); /* Pointer to Actual Page Header */ public void Clear() { this.iKey = 0; this.pNext = null; this.pCache = null; this.pPgHdr.Clear(); } }; /* ** Free slots in the allocator used to divide up the buffer provided using ** the SQLITE_CONFIG_PAGECACHE mechanism. */ public class PgFreeslot { public PgFreeslot pNext; /* Next free slot */ public PgHdr _PgHdr; /* Next Free Header */ }; /* ** Global data used by this cache. */ public class PCacheGlobal { public 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. */ public bool isInit; /* True if initialized */ public int szSlot; /* Size of each free slot */ public int nSlot; /* The number of pcache slots */ public int nReserve; /* Try to keep nFreeSlot above this */ public object pStart, pEnd; /* Bounds of pagecache malloc range */ /* Above requires no mutex. Use mutex below for variable that follow. */ public sqlite3_mutex mutex; /* Mutex for accessing the following: */ public int nFreeSlot; /* Number of unused pcache slots */ public 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. */ public bool bUnderPressure; /* True if low on PAGECACHE memory */ // C# public PCacheGlobal() { grp = new PGroup(); } } private static PCacheGlobal pcache = new PCacheGlobal(); /* ** 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)) private static PCacheGlobal pcache1 = pcache; /* ** When a PgHdr1 structure is allocated, the associated PCache1.szPage ** bytes of data are located directly before 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. ** ** Debug.Assert( PGHDR1_TO_PAGE(PAGE_TO_PGHDR1(pCache, X))==X ); */ //#define PGHDR1_TO_PAGE(p) (void)(((char)p) - p.pCache.szPage) private static PgHdr PGHDR1_TO_PAGE(PgHdr1 p) { return p.pPgHdr; } //#define PAGE_TO_PGHDR1(c, p) (PgHdr1)(((char)p) + c.szPage) private static PgHdr1 PAGE_TO_PGHDR1(PCache1 c, PgHdr p) { return p.pPgHdr1; } /* ** Macros to enter and leave the PCache LRU mutex. */ //#define pcache1EnterMutex(X) sqlite3_mutex_enter((X).mutex) private static void pcache1EnterMutex(PGroup X) { sqlite3_mutex_enter(X.mutex); } //#define pcache1LeaveMutex(X) sqlite3_mutex_leave((X).mutex) private static void pcache1LeaveMutex(PGroup X) { sqlite3_mutex_leave(X.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. ** ** This routine is called from sqlite3_initialize() and so it is guaranteed ** to be serialized already. There is no need for further mutexing. */ private static void sqlite3PCacheBufferSetup(object 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 = null; pcache1.pEnd = null; pcache1.pFree = null; pcache1.bUnderPressure = false; while (n-- > 0) { p = new PgFreeslot();// (PgFreeslot)pBuf; p._PgHdr = new PgHdr(); 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(). ** ** Multiple threads can run this routine at the same time. Global variables ** in pcache1 need to be protected via mutex. */ private static PgHdr pcache1Alloc(int nByte) { PgHdr p = null; Debug.Assert(sqlite3_mutex_notheld(pcache1.grp.mutex)); sqlite3StatusSet(SQLITE_STATUS_PAGECACHE_SIZE, nByte); if (nByte <= pcache1.szSlot) { sqlite3_mutex_enter(pcache1.mutex); p = pcache1.pFree._PgHdr; if (p != null) { pcache1.pFree = pcache1.pFree.pNext; pcache1.nFreeSlot--; pcache1.bUnderPressure = pcache1.nFreeSlot < pcache1.nReserve; Debug.Assert(pcache1.nFreeSlot >= 0); sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, 1); } sqlite3_mutex_leave(pcache1.mutex); } if (p == null) { /* Memory is not available in the SQLITE_CONFIG_PAGECACHE pool. Get ** it from sqlite3Malloc instead. */ p = new PgHdr();// sqlite3Malloc( nByte ); //if ( p != null ) { int sz = nByte;//sqlite3MallocSize( p ); sqlite3_mutex_enter(pcache1.mutex); sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz); sqlite3_mutex_leave(pcache1.mutex); } sqlite3MemdebugSetType(p, MEMTYPE_PCACHE); } return p; } /* ** Free an allocated buffer obtained from pcache1Alloc(). */ private static void pcache1Free(ref PgHdr p) { if (p == null) return; if (p.CacheAllocated)//if ( p >= pcache1.pStart && p < pcache1.pEnd ) { PgFreeslot pSlot = new PgFreeslot(); sqlite3_mutex_enter(pcache1.mutex); sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_USED, -1); pSlot._PgHdr = p;// pSlot = (PgFreeslot)p; pSlot.pNext = pcache1.pFree; pcache1.pFree = pSlot; pcache1.nFreeSlot++; pcache1.bUnderPressure = pcache1.nFreeSlot < pcache1.nReserve; Debug.Assert(pcache1.nFreeSlot <= pcache1.nSlot); sqlite3_mutex_leave(pcache1.mutex); } else { int iSize; Debug.Assert(sqlite3MemdebugHasType(p, MEMTYPE_PCACHE)); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); iSize = sqlite3MallocSize(p.pData); sqlite3_mutex_enter(pcache1.mutex); sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -iSize); sqlite3_mutex_leave(pcache1.mutex); sqlite3_free(ref p.pData); } } #if SQLITE_ENABLE_MEMORY_MANAGEMENT /* ** Return the size of a pcache allocation */ static int pcache1MemSize(object p){ if( p>=pcache1.pStart && p pGroup.nMaxPage && pGroup.pLruTail != null) { PgHdr1 p = pGroup.pLruTail; Debug.Assert(p.pCache.pGroup == pGroup); pcache1PinPage(p); pcache1RemoveFromHash(p); pcache1FreePage(ref 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 PCache mutex must be held when this function is called. */ private static void pcache1TruncateUnsafe( PCache1 pCache, /* The cache to truncate */ uint iLimit /* Drop pages with this pgno or larger */ ) { #if !NDEBUG || SQLITE_COVERAGE_TEST //TESTONLY( uint nPage = 0; ) /* To assert pCache.nPage is correct */ uint nPage = 0; #endif uint h; Debug.Assert(sqlite3_mutex_held(pCache.pGroup.mutex)); for (h = 0; h < pCache.nHash; h++) { PgHdr1 pPrev = null; PgHdr1 pp = pCache.apHash[h]; PgHdr1 pPage; while ((pPage = pp) != null) { if (pPage.iKey >= iLimit) { pCache.nPage--; pp = pPage.pNext; pcache1PinPage(pPage); if (pCache.apHash[h] == pPage) pCache.apHash[h] = pPage.pNext; else pPrev.pNext = pp; pcache1FreePage(ref pPage); } else { pp = pPage.pNext; #if !NDEBUG || SQLITE_COVERAGE_TEST //TESTONLY( nPage++; ) nPage++; #endif } pPrev = pPage; } } #if !NDEBUG || SQLITE_COVERAGE_TEST Debug.Assert(pCache.nPage == nPage); #endif } /******************************************************************************/ /******** sqlite3_pcache Methods **********************************************/ /* ** Implementation of the sqlite3_pcache.xInit method. */ private static int pcache1Init(T NotUsed) { UNUSED_PARAMETER(NotUsed); Debug.Assert(pcache1.isInit == false); pcache1 = new PCacheGlobal();//memset(&pcache1, 0, sizeof(pcache1)); if (sqlite3GlobalConfig.bCoreMutex) { 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 = true; return SQLITE_OK; } /* ** Implementation of the sqlite3_pcache.xShutdown method. ** Note that the static mutex allocated in xInit does ** not need to be freed. */ private static void pcache1Shutdown(T NotUsed) { UNUSED_PARAMETER(NotUsed); Debug.Assert(pcache1.isInit); pcache1 = new PCacheGlobal();//;memset( &pcache1, 0, sizeof( pcache1 ) ); } /* ** Implementation of the sqlite3_pcache.xCreate method. ** ** Allocate a new cache. */ private static sqlite3_pcache pcache1Create(int szPage, bool bPurgeable) { 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 (SQLITE_ENABLE_MEMORY_MANAGEMENT) || !SQLITE_THREADSAF const int separateCache = 0; #else int separateCache = sqlite3GlobalConfig.bCoreMutex>0; #endif //sz = sizeof( PCache1 ) + sizeof( PGroup ) * separateCache; pCache = new PCache1();//(PCache1)sqlite3_malloc( sz ); //if ( pCache != null ) //{ //memset( pCache, 0, sz ); if (separateCache == 0) { pGroup = pcache1.grp; } ////else ////{ ////pGroup = new PGroup();//(PGroup)pCache[1]; ////pGroup.mxPinned = 10; ////} pGroup = pcache1.grp; pCache.pGroup = pGroup; pCache.szPage = szPage; pCache.bPurgeable = bPurgeable;//( bPurgeable ? 1 : 0 ); if (bPurgeable) { pCache.nMin = 10; pcache1EnterMutex(pGroup); pGroup.nMinPage += (int)pCache.nMin; pGroup.mxPinned = pGroup.nMaxPage + 10 - pGroup.nMinPage; pcache1LeaveMutex(pGroup); } //} return (sqlite3_pcache)pCache; } /* ** Implementation of the sqlite3_pcache.xCachesize method. ** ** Configure the cache_size limit for a cache. */ private static void pcache1Cachesize(sqlite3_pcache p, int nMax) { PCache1 pCache = (PCache1)p; if (pCache.bPurgeable) { PGroup pGroup = pCache.pGroup; pcache1EnterMutex(pGroup); pGroup.nMaxPage += nMax - pCache.nMax; pGroup.mxPinned = pGroup.nMaxPage + 10 - pGroup.nMinPage; pCache.nMax = nMax; pCache.n90pct = pCache.nMax * 9 / 10; pcache1EnforceMaxPage(pGroup); pcache1LeaveMutex(pGroup); } } /* ** Implementation of the sqlite3_pcache.xPagecount method. */ private static int pcache1Pagecount(sqlite3_pcache p) { int n; PCache1 pCache = (PCache1)p; pcache1EnterMutex(pCache.pGroup); n = (int)pCache.nPage; pcache1LeaveMutex(pCache.pGroup); 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. 0 means do not allocate a new ** page. 1 means allocate a new page if space is easily available. 2 ** means to try really hard to allocate a new page. ** ** For a non-purgeable cache (a cache used as the storage for an in-memory ** database) there is really no difference between createFlag 1 and 2. So ** the calling function (pcache.c) will never have a createFlag of 1 on ** a non-purgable cache. ** ** 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, 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, 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: ** ** (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, ** ** (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. ** ** 5. Otherwise, allocate and return a new page buffer. */ private static PgHdr pcache1Fetch(sqlite3_pcache p, Pgno iKey, int createFlag) { int nPinned; PCache1 pCache = (PCache1)p; PGroup pGroup; PgHdr1 pPage = null; Debug.Assert(pCache.bPurgeable || createFlag != 1); Debug.Assert(pCache.bPurgeable || pCache.nMin == 0); Debug.Assert(pCache.bPurgeable == false || pCache.nMin == 10); Debug.Assert(pCache.nMin == 0 || pCache.bPurgeable); pcache1EnterMutex(pGroup = pCache.pGroup); /* Step 1: Search the hash table for an existing entry. */ if (pCache.nHash > 0) { int h = (int)(iKey % pCache.nHash); for (pPage = pCache.apHash[h]; pPage != null && pPage.iKey != iKey; pPage = pPage.pNext) ; } /* Step 2: Abort if no existing page is found and createFlag is 0 */ if (pPage != null || createFlag == 0) { pcache1PinPage(pPage); goto fetch_out; } /* 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. */ #if 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; Debug.Assert(nPinned >= 0); Debug.Assert(pGroup.mxPinned == pGroup.nMaxPage + 10 - pGroup.nMinPage); Debug.Assert(pCache.n90pct == pCache.nMax * 9 / 10); if (createFlag == 1 && ( nPinned >= pGroup.mxPinned || nPinned >= (int)pCache.n90pct || pcache1UnderMemoryPressure(pCache) )) { goto fetch_out; } if (pCache.nPage >= pCache.nHash && pcache1ResizeHash(pCache) != 0) { goto fetch_out; } /* Step 4. Try to recycle a page. */ if (pCache.bPurgeable && pGroup.pLruTail != null && ( (pCache.nPage + 1 >= pCache.nMax) || pGroup.nCurrentPage >= pGroup.nMaxPage || pcache1UnderMemoryPressure(pCache) )) { PCache1 pOtherCache; pPage = pGroup.pLruTail; pcache1RemoveFromHash(pPage); pcache1PinPage(pPage); if ((pOtherCache = pPage.pCache).szPage != pCache.szPage) { pcache1FreePage(ref pPage); pPage = null; } else { pGroup.nCurrentPage -= (pOtherCache.bPurgeable ? 1 : 0) - (pCache.bPurgeable ? 1 : 0); } } /* Step 5. If a usable page buffer has still not been found, ** attempt to allocate a new one. */ if (null == pPage) { if (createFlag == 1) sqlite3BeginBenignMalloc(); pcache1LeaveMutex(pGroup); pPage = pcache1AllocPage(pCache); pcache1EnterMutex(pGroup); if (createFlag == 1) sqlite3EndBenignMalloc(); } if (pPage != null) { int h = (int)(iKey % pCache.nHash); pCache.nPage++; pPage.iKey = iKey; pPage.pNext = pCache.apHash[h]; pPage.pCache = pCache; pPage.pLruPrev = null; pPage.pLruNext = null; PGHDR1_TO_PAGE(pPage).Clear();// *(void **)(PGHDR1_TO_PAGE(pPage)) = 0; pPage.pPgHdr.pPgHdr1 = pPage; pCache.apHash[h] = pPage; } fetch_out: if (pPage != null && iKey > pCache.iMaxKey) { pCache.iMaxKey = iKey; } pcache1LeaveMutex(pGroup); return (pPage != null ? PGHDR1_TO_PAGE(pPage) : null); } /* ** Implementation of the sqlite3_pcache.xUnpin method. ** ** Mark a page as unpinned (eligible for asynchronous recycling). */ private static void pcache1Unpin(sqlite3_pcache p, PgHdr pPg, bool reuseUnlikely) { PCache1 pCache = (PCache1)p; PgHdr1 pPage = PAGE_TO_PGHDR1(pCache, pPg); PGroup pGroup = pCache.pGroup; Debug.Assert(pPage.pCache == pCache); pcache1EnterMutex(pGroup); /* It is an error to call this function if the page is already ** part of the PGroup LRU list. */ Debug.Assert(pPage.pLruPrev == null && pPage.pLruNext == null); Debug.Assert(pGroup.pLruHead != pPage && pGroup.pLruTail != pPage); if (reuseUnlikely || pGroup.nCurrentPage > pGroup.nMaxPage) { pcache1RemoveFromHash(pPage); pcache1FreePage(ref pPage); } else { /* Add the page to the PGroup LRU list. */ if (pGroup.pLruHead != null) { pGroup.pLruHead.pLruPrev = pPage; pPage.pLruNext = pGroup.pLruHead; pGroup.pLruHead = pPage; } else { pGroup.pLruTail = pPage; pGroup.pLruHead = pPage; } pCache.nRecyclable++; } pcache1LeaveMutex(pCache.pGroup); } /* ** Implementation of the sqlite3_pcache.xRekey method. */ private static void pcache1Rekey( sqlite3_pcache p, PgHdr pPg, Pgno iOld, Pgno iNew ) { PCache1 pCache = (PCache1)p; PgHdr1 pPage = PAGE_TO_PGHDR1(pCache, pPg); PgHdr1 pp; int h; Debug.Assert(pPage.iKey == iOld); Debug.Assert(pPage.pCache == pCache); pcache1EnterMutex(pCache.pGroup); h = (int)(iOld % pCache.nHash); pp = pCache.apHash[h]; while ((pp) != pPage) { pp = (pp).pNext; } if (pp == pCache.apHash[h]) pCache.apHash[h] = pp.pNext; else pp.pNext = pPage.pNext; h = (int)(iNew % pCache.nHash); pPage.iKey = iNew; pPage.pNext = pCache.apHash[h]; pCache.apHash[h] = pPage; if (iNew > pCache.iMaxKey) { pCache.iMaxKey = iNew; } pcache1LeaveMutex(pCache.pGroup); } /* ** 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. */ private static void pcache1Truncate(sqlite3_pcache p, Pgno iLimit) { PCache1 pCache = (PCache1)p; pcache1EnterMutex(pCache.pGroup); if (iLimit <= pCache.iMaxKey) { pcache1TruncateUnsafe(pCache, iLimit); pCache.iMaxKey = iLimit - 1; } pcache1LeaveMutex(pCache.pGroup); } /* ** Implementation of the sqlite3_pcache.xDestroy method. ** ** Destroy a cache allocated using pcache1Create(). */ private static void pcache1Destroy(ref sqlite3_pcache p) { PCache1 pCache = (PCache1)p; PGroup pGroup = pCache.pGroup; Debug.Assert(pCache.bPurgeable || (pCache.nMax == 0 && pCache.nMin == 0)); pcache1EnterMutex(pGroup); pcache1TruncateUnsafe(pCache, 0); 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 ); p = null; } /* ** This function is called during initialization (sqlite3_initialize()) to ** install the default pluggable cache module, assuming the user has not ** already provided an alternative. */ private static void sqlite3PCacheSetDefault() { sqlite3_pcache_methods defaultMethods = new sqlite3_pcache_methods( 0, /* pArg */ (dxPC_Init)pcache1Init, /* xInit */ (dxPC_Shutdown)pcache1Shutdown, /* xShutdown */ (dxPC_Create)pcache1Create, /* xCreate */ (dxPC_Cachesize)pcache1Cachesize, /* xCachesize */ (dxPC_Pagecount)pcache1Pagecount, /* xPagecount */ (dxPC_Fetch)pcache1Fetch, /* xFetch */ (dxPC_Unpin)pcache1Unpin, /* xUnpin */ (dxPC_Rekey)pcache1Rekey, /* xRekey */ (dxPC_Truncate)pcache1Truncate, /* xTruncate */ (dxPC_Destroy)pcache1Destroy /* xDestroy */ ); sqlite3_config(SQLITE_CONFIG_PCACHE, defaultMethods); } #if 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; Debug.Assert( sqlite3_mutex_notheld(pcache1.grp.mutex) ); Debug.Assert( sqlite3_mutex_notheld(pcache1.mutex) ); if( pcache1.pStart==0 ){ PgHdr1 p; pcache1EnterMutex(&pcache1.grp); while( (nReq<0 || nFree