/* ** 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) { Debug.Assert(sqlite3_mutex_held(pcache1.mutex)); if (pPage != null && (pPage.pLruNext != null || pPage == pcache1.pLruTail)) { if (pPage.pLruPrev != null) { pPage.pLruPrev.pLruNext = pPage.pLruNext; } if (pPage.pLruNext != null) { pPage.pLruNext.pLruPrev = pPage.pLruPrev; } if (pcache1.pLruHead == pPage) { pcache1.pLruHead = pPage.pLruNext; } if (pcache1.pLruTail == pPage) { pcache1.pLruTail = pPage.pLruPrev; } pPage.pLruNext = null; pPage.pLruPrev = null; pPage.pCache.nRecyclable--; } }
/* ** This function is used internally to remove the page pPage from the ** 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 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) { PCache1 pCache; PGroup pGroup; if (pPage == null) { return; } pCache = pPage.pCache; pGroup = pCache.pGroup; Debug.Assert(sqlite3_mutex_held(pGroup.mutex)); if (pPage.pLruNext != null || pPage == pGroup.pLruTail) { if (pPage.pLruPrev != null) { pPage.pLruPrev.pLruNext = pPage.pLruNext; } if (pPage.pLruNext != null) { pPage.pLruNext.pLruPrev = pPage.pLruPrev; } if (pGroup.pLruHead == pPage) { pGroup.pLruHead = pPage.pLruNext; } if (pGroup.pLruTail == pPage) { pGroup.pLruTail = pPage.pLruPrev; } pPage.pLruNext = null; pPage.pLruPrev = null; 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) { u32 h; PCache1 pCache = pPage.pCache; PgHdr1 pp, pPrev; h = pPage.iKey % pCache.nHash; pPrev = null; for (pp = pCache.apHash[h]; pp != pPage; pPrev = pp, pp = pp.pNext) { ; } if (pPrev == null) { pCache.apHash[h] = pp.pNext; } else { pPrev.pNext = pp.pNext; // pCache.apHash[h] = pp.pNext; } pCache.nPage--; #if FALSE Debug.Assert(pcache1CountHash(pCache) == pCache.nPage); #endif }
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(); }
public PgHdr pPgHdr = new PgHdr(); /* Pointer to Actual Page Header */ public void Clear() { iKey = 0; pNext = null; pCache = null; pPgHdr.Clear(); }
/* ** 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() { Debug.Assert(sqlite3_mutex_held(pcache1.mutex)); while (pcache1.nCurrentPage > pcache1.nMaxPage && pcache1.pLruTail != null) { PgHdr1 p = pcache1.pLruTail; pcache1PinPage(p); pcache1RemoveFromHash(p); pcache1FreePage(ref p); } }
/* ** Free a page object allocated by pcache1AllocPage(). ** ** The pointer is allowed to be NULL, which is prudent. But it turns out ** that the current implementation happens to never call this routine ** with a NULL pointer, so we mark the NULL test with ALWAYS(). */ static void pcache1FreePage(ref PgHdr1 p) { if (ALWAYS(p != null)) { if (p.pCache.bPurgeable) { pcache1.nCurrentPage--; } pcache1Free(ref p.pPgHdr);//PGHDR1_TO_PAGE( p ); } }
/* ** Free a page object allocated by pcache1AllocPage(). ** ** The pointer is allowed to be NULL, which is prudent. But it turns out ** that the current implementation happens to never call this routine ** with a NULL pointer, so we mark the NULL test with ALWAYS(). */ static void pcache1FreePage(ref PgHdr1 p) { if (ALWAYS(p)) { PCache1 pCache = p.pCache; if (pCache.bPurgeable) { pCache.pGroup.nCurrentPage--; } pcache1Free(ref p.pPgHdr);//PGHDR1_TO_PAGE( p ); } }
/* ** If there are currently more than nMaxPage pages allocated, try ** to recycle pages to reduce the number allocated to nMaxPage. */ static void pcache1EnforceMaxPage(PGroup pGroup) { Debug.Assert(sqlite3_mutex_held(pGroup.mutex)); while (pGroup.nCurrentPage > 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 global mutex must be held when this function is called. */ private static void pcache1TruncateUnsafe( PCache1 pCache, uint iLimit ) { //TESTONLY( unsigned int nPage = 0; ) /* Used to assert pCache->nPage is correct */ #if !NDEBUG || SQLITE_COVERAGE_TEST u32 nPage = 0; #endif uint h; Debug.Assert(sqlite3_mutex_held(pcache1.mutex)); for (h = 0; h < pCache.nHash; h++) { PgHdr1 pPrev = null; var pp = pCache.apHash[h]; PgHdr1 pPage; while ((pPage = pp) != null) { if (pPage.iKey >= iLimit) { pp = pPage.pNext; pcache1PinPage(pPage); if (pCache.apHash[h] == pPage) { pCache.apHash[h] = pPage.pNext; } else { pPrev.pNext = pp; } pcache1FreePage(ref pPage); pCache.nPage--; #if FALSE Debug.Assert(pcache1CountHash(pCache) == pCache.nPage); #endif } else { pp = pPage.pNext; //TESTONLY( nPage++; ) #if !NDEBUG || SQLITE_COVERAGE_TEST nPage++; #endif } pPrev = pPage; } } #if !NDEBUG || SQLITE_COVERAGE_TEST Debug.Assert(pCache.nPage == nPage); #endif }
/* ** Implementation of the sqlite3_pcache.xRekey method. */ static void pcache1Rekey( sqlite3_pcache p, PgHdr pPg, u32 iOld, u32 iNew ) { PCache1 pCache = p; PgHdr1 pPage = PAGE_TO_PGHDR1(pCache, pPg); PgHdr1 pp; u32 h; Debug.Assert(pPage.iKey == iOld); Debug.Assert(pPage.pCache == pCache); pcache1EnterMutex(); h = 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 = iNew % pCache.nHash; 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)) { pCache.iMaxKey = iNew; } pcache1LeaveMutex(); }
static u32 pcache1CountHash(PCache1 pCache) { u32 nPage = 0; for (u32 h = 0; h < pCache.nHash; h++) { PgHdr1 pp = pCache.apHash[h]; while (pp != null) { nPage++; pp = pp.pNext; } } return(nPage); }
/******************************************************************************/ /******** 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. */ private static int pcache1ResizeHash(PCache1 p) { PgHdr1[] apNew; uint nNew; uint i; Debug.Assert(sqlite3_mutex_held(pcache1.mutex)); nNew = p.nHash * 2; if (nNew < 256) { nNew = 256; } pcache1LeaveMutex(); if (p.nHash != 0) { sqlite3BeginBenignMalloc(); } apNew = new PgHdr1[nNew]; // (PgHdr1**)sqlite3_malloc( sizeof( PgHdr1* ) * nNew ); if (p.nHash != 0) { sqlite3EndBenignMalloc(); } pcache1EnterMutex(); if (apNew != null) { //memset(apNew, 0, sizeof(PgHdr1 *)*nNew); for (i = 0; i < p.nHash; i++) { PgHdr1 pPage; var pNext = p.apHash[i]; while ((pPage = pNext) != null) { var h = pPage.iKey % nNew; pNext = pPage.pNext; pPage.pNext = apNew[h]; apNew[h] = pPage; } } //sqlite3_free( ref p.apHash ); p.apHash = apNew; p.nHash = nNew; } return(p.apHash != null ? SQLITE_OK : SQLITE_NOMEM); }
/* ** 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, u32 iLimit ) { //TESTONLY( unsigned int nPage = 0; ) /* Used to assert pCache->nPage is correct */ #if !NDEBUG || SQLITE_COVERAGE_TEST u32 nPage = 0; #endif u32 h; Debug.Assert(sqlite3_mutex_held(pcache1.mutex)); for (h = 0; h < pCache.nHash; h++) { 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 { Debugger.Break(); } pcache1FreePage(ref pPage); } else { pp = pPage.pNext; //TESTONLY( nPage++; ) #if !NDEBUG || SQLITE_COVERAGE_TEST nPage++; #endif } } } #if !NDEBUG || SQLITE_COVERAGE_TEST Debug.Assert(pCache.nPage == nPage); #endif }
/* ** 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. */ 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 }
public void Clear() { this.pData = null; this.pExtra = null; this.pDirty = null; this.pgno = 0; this.pPager = null; #if SQLITE_CHECK_PAGES this.pageHash = 0; #endif this.flags = 0; this.nRef = 0; this.pCache = null; this.pDirtyNext = null; this.pDirtyPrev = null; this.pPgHdr1 = null; }
public void Clear() { sqlite3_free(ref pData); pData = null; pExtra = null; pDirty = null; pgno = 0; pPager = null; #if SQLITE_CHECK_PAGES this.pageHash = 0; #endif flags = 0; nRef = 0; pCache = null; pDirtyNext = null; pDirtyPrev = null; pPgHdr1 = null; }
/* ** Implementation of the sqlite3_pcache.xRekey method. */ 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); }
public void Clear() { sqlite3_free(ref this.pData); this.pData = null; this.pExtra = null; this.pDirty = null; this.pgno = 0; this.pPager = null; #if SQLITE_CHECK_PAGES this.pageHash = 0; #endif this.flags = 0; this.nRef = 0; this.CacheAllocated = false; this.pCache = null; this.pDirtyNext = null; this.pDirtyPrev = null; this.pPgHdr1 = null; }
public void Clear() { sqlite3_free(ref this.pData); this.pData = null; this.pExtra = null; this.pDirty = null; this.pgno = 0; this.pPager = null; #if SQLITE_CHECK_PAGES this.pageHash=0; #endif this.flags = 0; this.nRef = 0; this.CacheAllocated = false; this.pCache = null; this.pDirtyNext = null; this.pDirtyPrev = null; this.pPgHdr1 = null; }
/* ** Implementation of the sqlite3_pcache.xRekey method. */ static void pcache1Rekey( sqlite3_pcache p, PgHdr pPg, u32 iOld, u32 iNew ) { PCache1 pCache = p; PgHdr1 pPage = PAGE_TO_PGHDR1(pCache, pPg); PgHdr1 pp; u32 h; Debug.Assert(pPage.iKey == iOld); Debug.Assert(pPage.pCache == pCache); pcache1EnterMutex(); h = 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 = iNew % pCache.nHash; pPage.iKey = iNew; pPage.pNext = pCache.apHash[h]; pCache.apHash[h] = pPage; if (iNew > pCache.iMaxKey) { pCache.iMaxKey = iNew; } pcache1LeaveMutex(); }
/* ** Implementation of the sqlite3_pcache.xUnpin method. ** ** Mark a page as unpinned (eligible for asynchronous recycling). */ static void pcache1Unpin(sqlite3_pcache p, PgHdr pPg, int reuseUnlikely) { PCache1 pCache = (PCache1)p; PgHdr1 pPage = PAGE_TO_PGHDR1(pCache, pPg); Debug.Assert(pPage.pCache == pCache); pcache1EnterMutex(); /* It is an error to call this function if the page is already ** part of the global LRU list. */ Debug.Assert(pPage.pLruPrev == null && pPage.pLruNext == null); Debug.Assert(pcache1.pLruHead != pPage && pcache1.pLruTail != pPage); if (reuseUnlikely != 0 || pcache1.nCurrentPage > pcache1.nMaxPage) { pcache1RemoveFromHash(pPage); pcache1FreePage(ref 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 != null) { 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.xUnpin method. ** ** Mark a page as unpinned (eligible for asynchronous recycling). */ 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); }
/* ** 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) { u32 h; PCache1 pCache = pPage.pCache; PgHdr1 pp, pPrev; h = pPage.iKey % pCache.nHash; pPrev = null; for (pp = pCache.apHash[h]; pp != pPage; pPrev = pp, pp = pp.pNext) { ; } if (pPrev == null) { pCache.apHash[h] = pp.pNext; } else { pPrev.pNext = pp.pNext; // pCache.apHash[h] = pp.pNext; } pCache.nPage--; }
/* ** Remove the page supplied as an argument from the hash table ** (PCache1.apHash structure) that it is currently stored in. ** ** The PGroup mutex must be held when this function is called. */ static void pcache1RemoveFromHash(PgHdr1 pPage) { int h; PCache1 pCache = pPage.pCache; PgHdr1 pp; PgHdr1 pPrev = null; Debug.Assert(sqlite3_mutex_held(pCache.pGroup.mutex)); h = (int)(pPage.iKey % pCache.nHash); for (pp = pCache.apHash[h]; pp != pPage; pPrev = pp, pp = pp.pNext) { ; } if (pPrev == null) { pCache.apHash[h] = pp.pNext; } else { pPrev.pNext = pp.pNext; // pCache.apHash[h] = pp.pNext; } pCache.nPage--; }
/* ** Allocate a new page object initially associated with cache pCache. */ static PgHdr1 pcache1AllocPage(PCache1 pCache) { //int nByte = sizeof( PgHdr1 ) + pCache.szPage; PgHdr pPg = pcache1Alloc(pCache.szPage);//nByte ); PgHdr1 p = null; //if ( pPg !=null) { //PAGE_TO_PGHDR1( pCache, pPg ); p = new PgHdr1(); p.pCache = pCache; p.pPgHdr = pPg; if (pCache.bPurgeable) { pCache.pGroup.nCurrentPage++; } } //else //{ // p = 0; //} return(p); }
/******************************************************************************/ /******** 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; u32 nNew; u32 i; Debug.Assert( sqlite3_mutex_held( pcache1.mutex ) ); nNew = p.nHash * 2; if ( nNew < 256 ) { nNew = 256; } pcache1LeaveMutex(); if ( p.nHash != 0 ) { sqlite3BeginBenignMalloc(); } apNew = new PgHdr1[nNew];// (PgHdr1**)sqlite3_malloc( sizeof( PgHdr1* ) * nNew ); if ( p.nHash != 0 ) { sqlite3EndBenignMalloc(); } pcache1EnterMutex(); if ( apNew != null ) { //memset(apNew, 0, sizeof(PgHdr1 *)*nNew); for ( i = 0; i < p.nHash; i++ ) { PgHdr1 pPage; PgHdr1 pNext = p.apHash[i]; while ( ( pPage = pNext ) != null ) { u32 h = (u32)( pPage.iKey % nNew ); pNext = pPage.pNext; pPage.pNext = apNew[h]; apNew[h] = pPage; } } //sqlite3_free( ref p.apHash ); p.apHash = apNew; p.nHash = nNew; } return ( p.apHash != null ? SQLITE_OK : SQLITE_NOMEM ); }
/* ** 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. */ 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); }
/* ** 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 ) { u32 h; PCache1 pCache = pPage.pCache; PgHdr1 pp, pPrev; h = pPage.iKey % pCache.nHash; pPrev = null; for ( pp = pCache.apHash[h]; pp != pPage; pPrev = pp, pp = pp.pNext ) ; if ( pPrev == null ) pCache.apHash[h] = pp.pNext; else pPrev.pNext = pp.pNext; // pCache.apHash[h] = pp.pNext; pCache.nPage--; #if FALSE Debug.Assert(pcache1CountHash(pCache)==pCache.nPage); #endif }
/* ** Free a page object allocated by pcache1AllocPage(). ** ** The pointer is allowed to be NULL, which is prudent. But it turns out ** that the current implementation happens to never call this routine ** with a NULL pointer, so we mark the NULL test with ALWAYS(). */ static void pcache1FreePage( ref PgHdr1 p ) { if ( ALWAYS( p != null ) ) { if ( p.pCache.bPurgeable ) { pcache1.nCurrentPage--; } pcache1Free( ref p.pPgHdr );//PGHDR1_TO_PAGE( p ); } }
/* ** Allocate a new page object initially associated with cache pCache. */ static PgHdr1 pcache1AllocPage( PCache1 pCache ) { //int nByte = sizeof(PgHdr1) + pCache.szPage; PgHdr pPg = pcache1Alloc( pCache.szPage ); PgHdr1 p; //if ( pPg != null ) { // PAGE_TO_PGHDR1( pCache, pPg ); p = new PgHdr1(); p.pCache = pCache; p.pPgHdr = pPg; if ( pCache.bPurgeable ) { pcache1.nCurrentPage++; } } //else //{ // p = null; //} return p; }
/* ** 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) static PgHdr PGHDR1_TO_PAGE(PgHdr1 p) { return(p.pPgHdr); }
/* ** 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. ** ** assert( PGHDR1_TO_PAGE(PAGE_TO_PGHDR1(pCache, X))==X ); */ //#define PGHDR1_TO_PAGE(p) (void*)(((char*)p) - p->pCache->szPage) static PgHdr PGHDR1_TO_PAGE( PgHdr1 p ) { return p.pPgHdr; }
/* ** 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 ) { u32 h; PCache1 pCache = pPage.pCache; PgHdr1 pp, pPrev; h = pPage.iKey % pCache.nHash; pPrev = null; for ( pp = pCache.apHash[h] ; pp != pPage ; pPrev = pp, pp = pp.pNext ) ; if ( pPrev == null ) pCache.apHash[h] = pp.pNext; else pPrev.pNext = pp.pNext; // pCache.apHash[h] = pp.pNext; pCache.nPage--; }
/* ** 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 ) { Debug.Assert( sqlite3_mutex_held( pcache1.mutex ) ); if ( pPage != null && ( pPage.pLruNext != null || pPage == pcache1.pLruTail ) ) { if ( pPage.pLruPrev != null ) { pPage.pLruPrev.pLruNext = pPage.pLruNext; } if ( pPage.pLruNext != null ) { pPage.pLruNext.pLruPrev = pPage.pLruPrev; } if ( pcache1.pLruHead == pPage ) { pcache1.pLruHead = pPage.pLruNext; } if ( pcache1.pLruTail == pPage ) { pcache1.pLruTail = pPage.pLruPrev; } pPage.pLruNext = null; pPage.pLruPrev = null; pPage.pCache.nRecyclable--; } }
/* ** 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, ** 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 PgHdr pcache1Fetch(sqlite3_pcache p, u32 iKey, int createFlag) { u32 nPinned; PCache1 pCache = p; PgHdr1 pPage = null; Debug.Assert(pCache.bPurgeable || createFlag != 1); pcache1EnterMutex(); if (createFlag == 1) { sqlite3BeginBenignMalloc(); } /* Search the hash table for an existing entry. */ if (pCache.nHash > 0) { u32 h = iKey % pCache.nHash; for (pPage = pCache.apHash[h]; pPage != null && pPage.iKey != iKey; pPage = pPage.pNext) { ; } } if (pPage != null || createFlag == 0) { pcache1PinPage(pPage); goto fetch_out; } /* Step 3 of header comment. */ nPinned = pCache.nPage - pCache.nRecyclable; if (createFlag == 1 && ( nPinned >= (pcache1.nMaxPage + pCache.nMin - pcache1.nMinPage) || nPinned >= (pCache.nMax * 9 / 10) )) { goto fetch_out; } if (pCache.nPage >= pCache.nHash && pcache1ResizeHash(pCache) != 0) { goto fetch_out; } /* Step 4. Try to recycle a page buffer if appropriate. */ if (pCache.bPurgeable && pcache1.pLruTail != null && ( pCache.nPage + 1 >= pCache.nMax || pcache1.nCurrentPage >= pcache1.nMaxPage )) { pPage = pcache1.pLruTail; pcache1RemoveFromHash(pPage); pcache1PinPage(pPage); if (pPage.pCache.szPage != pCache.szPage) { pcache1FreePage(ref pPage); pPage = null; } else { pcache1.nCurrentPage -= ((pPage.pCache.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) { pPage = pcache1AllocPage(pCache); } if (pPage != null) { u32 h = 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; #if FALSE Debug.Assert(pcache1CountHash(pCache) == pCache.nPage); #endif } fetch_out: if (pPage != null && iKey > pCache.iMaxKey) { pCache.iMaxKey = iKey; } if (createFlag == 1) { sqlite3EndBenignMalloc(); } pcache1LeaveMutex(); return(pPage != null ? PGHDR1_TO_PAGE(pPage) : null); }
public void Clear() { this.pData = null; this.pExtra = null; this.pDirty = null; this.pgno = 0; this.pPager = null; #if SQLITE_CHECK_PAGES this.pageHash=0; #endif this.flags = 0; this.nRef = 0; this.pCache = null; this.pDirtyNext = null; this.pDirtyPrev = null; this.pPgHdr1 = null; }