// was:sqlite3PcacheMove internal static void MovePage(PgHdr p, Pgno newPgno) { PCache pCache = p.Cache; Debug.Assert(p.Refs > 0); Debug.Assert(newPgno > 0); pCache.pCache.xRekey(p, p.ID, newPgno); p.ID = newPgno; if ((p.Flags & PgHdr.PGHDR.DIRTY) != 0 && (p.Flags & PgHdr.PGHDR.NEED_SYNC) != 0) { pcacheRemoveFromDirtyList(p); pcacheAddToDirtyList(p); } }
public void ClearState() { this.Data = null; this.Extra = null; this.Dirtys = null; this.ID = 0; this.Pager = null; #if DEBUG this.PageHash = 0; #endif this.Flags = 0; this.Refs = 0; this.CacheAllocated = false; this.Cache = null; this.DirtyNext = null; this.DirtyPrev = null; this.PgHdr1 = null; }
// was:sqlite3PagerPageRefcount public static int GetPageRefCount(DbPage pPage) { return(PCache.sqlite3PcachePageRefcount(pPage)); }
// was:sqlite3PagerRef public static void AddPageRef(DbPage pPg) { PCache.AddPageRef(pPg); }
private static RC pager_write(PgHdr pPg) { var pData = pPg.Data; var pPager = pPg.Pager; var rc = RC.OK; // This routine is not called unless a write-transaction has already been started. The journal file may or may not be open at this point. // It is never called in the ERROR state. Debug.Assert(pPager.eState == PAGER.WRITER_LOCKED || pPager.eState == PAGER.WRITER_CACHEMOD || pPager.eState == PAGER.WRITER_DBMOD); Debug.Assert(pPager.assert_pager_state()); // If an error has been previously detected, report the same error again. This should not happen, but the check provides robustness. if (Check.NEVER(pPager.errCode) != RC.OK) { return(pPager.errCode); } // Higher-level routines never call this function if database is not writable. But check anyway, just for robustness. if (Check.NEVER(pPager.readOnly)) { return(RC.PERM); } #if SQLITE_CHECK_PAGES CHECK_PAGE(pPg); #endif // The journal file needs to be opened. Higher level routines have already obtained the necessary locks to begin the write-transaction, but the // rollback journal might not yet be open. Open it now if this is the case. // // This is done before calling sqlite3PcacheMakeDirty() on the page. Otherwise, if it were done after calling sqlite3PcacheMakeDirty(), then // an error might occur and the pager would end up in WRITER_LOCKED state with pages marked as dirty in the cache. if (pPager.eState == PAGER.WRITER_LOCKED) { rc = pPager.pager_open_journal(); if (rc != RC.OK) { return(rc); } } Debug.Assert(pPager.eState >= PAGER.WRITER_CACHEMOD); Debug.Assert(pPager.assert_pager_state()); // Mark the page as dirty. If the page has already been written to the journal then we can return right away. PCache.MakePageDirty(pPg); if (pageInJournal(pPg) && !subjRequiresPage(pPg)) { Debug.Assert(!pPager.pagerUseWal()); } else { // The transaction journal now exists and we have a RESERVED or an EXCLUSIVE lock on the main database file. Write the current page to // the transaction journal if it is not there already. if (!pageInJournal(pPg) && !pPager.pagerUseWal()) { Debug.Assert(!pPager.pagerUseWal()); if (pPg.ID <= pPager.dbOrigSize && pPager.jfd.IsOpen) { var iOff = pPager.journalOff; // We should never write to the journal file the page that contains the database locks. The following Debug.Assert verifies that we do not. Debug.Assert(pPg.ID != ((VirtualFile.PENDING_BYTE / (pPager.pageSize)) + 1)); Debug.Assert(pPager.journalHdr <= pPager.journalOff); byte[] pData2 = null; if (CODEC2(pPager, pData, pPg.ID, codec_ctx.ENCRYPT_READ_CTX, ref pData2)) { return(RC.NOMEM); } var cksum = pPager.pager_cksum(pData2); // Even if an IO or diskfull error occurred while journalling the page in the block above, set the need-sync flag for the page. // Otherwise, when the transaction is rolled back, the logic in playback_one_page() will think that the page needs to be restored // in the database file. And if an IO error occurs while doing so, then corruption may follow. pPg.Flags |= PgHdr.PGHDR.NEED_SYNC; rc = pPager.jfd.WriteByte(iOff, pPg.ID); if (rc != RC.OK) { return(rc); } rc = pPager.jfd.Write(pData2, pPager.pageSize, iOff + 4); if (rc != RC.OK) { return(rc); } rc = pPager.jfd.WriteByte(iOff + pPager.pageSize + 4, cksum); if (rc != RC.OK) { return(rc); } SysEx.IOTRACE("JOUT {0:x} {1} {2,11} {3}", pPager.GetHashCode(), pPg.ID, pPager.journalOff, pPager.pageSize); PAGERTRACE("JOURNAL {0} page {1} needSync={2} hash({3,08:x})", PAGERID(pPager), pPg.ID, (pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0 ? 1 : 0, pager_pagehash(pPg)); pPager.journalOff += 8 + pPager.pageSize; pPager.nRec++; Debug.Assert(pPager.pInJournal != null); rc = pPager.pInJournal.Set(pPg.ID); Debug.Assert(rc == RC.OK || rc == RC.NOMEM); rc |= pPager.addToSavepointBitvecs(pPg.ID); if (rc != RC.OK) { Debug.Assert(rc == RC.NOMEM); return(rc); } } else { if (pPager.eState != PAGER.WRITER_DBMOD) { pPg.Flags |= PgHdr.PGHDR.NEED_SYNC; } PAGERTRACE("APPEND {0} page {1} needSync={2}", PAGERID(pPager), pPg.ID, (pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0 ? 1 : 0); } } // If the statement journal is open and the page is not in it, then write the current page to the statement journal. Note that // the statement journal format differs from the standard journal format in that it omits the checksums and the header. if (subjRequiresPage(pPg)) { rc = subjournalPage(pPg); } } // Update the database size and return. if (pPager.dbSize < pPg.ID) { pPager.dbSize = pPg.ID; } return(rc); }
private RC pager_playback_one_page(ref long pOffset, BitArray pDone, int isMainJrnl, int isSavepnt) { Debug.Assert((isMainJrnl & ~1) == 0); // isMainJrnl is 0 or 1 Debug.Assert((isSavepnt & ~1) == 0); // isSavepnt is 0 or 1 Debug.Assert(isMainJrnl != 0 || pDone != null); // pDone always used on sub-journals Debug.Assert(isSavepnt != 0 || pDone == null); // pDone never used on non-savepoint var aData = this.pTmpSpace; Debug.Assert(aData != null); // Temp storage must have already been allocated Debug.Assert(!pagerUseWal() || (0 == isMainJrnl && isSavepnt != 0)); // Either the state is greater than PAGER_WRITER_CACHEMOD (a transaction or savepoint rollback done at the request of the caller) or this is // a hot-journal rollback. If it is a hot-journal rollback, the pager is in state OPEN and holds an EXCLUSIVE lock. Hot-journal rollback // only reads from the main journal, not the sub-journal. Debug.Assert(this.eState >= PAGER.WRITER_CACHEMOD || (this.eState == PAGER.OPEN && this.eLock == VFSLOCK.EXCLUSIVE)); Debug.Assert(this.eState >= PAGER.WRITER_CACHEMOD || isMainJrnl != 0); // Read the page number and page data from the journal or sub-journal file. Return an error code to the caller if an IO error occurs. var jfd = (isMainJrnl != 0 ? this.jfd : this.sjfd); // The file descriptor for the journal file Pgno pgno = 0; // The page number of a page in journal var rc = jfd.ReadByte(pOffset, ref pgno); if (rc != RC.OK) { return(rc); } rc = jfd.Read(aData, this.pageSize, pOffset + 4); if (rc != RC.OK) { return(rc); } pOffset += this.pageSize + 4 + isMainJrnl * 4; // Sanity checking on the page. This is more important that I originally thought. If a power failure occurs while the journal is being written, // it could cause invalid data to be written into the journal. We need to detect this invalid data (with high probability) and ignore it. if (pgno == 0 || pgno == PAGER_MJ_PGNO(this)) { Debug.Assert(0 == isSavepnt); return(RC.DONE); } if (pgno > this.dbSize || pDone.Get(pgno) != 0) { return(RC.OK); } uint cksum = 0; // Checksum used for sanity checking if (isMainJrnl != 0) { rc = jfd.ReadByte((pOffset) - 4, ref cksum); if (rc != RC.OK) { return(rc); } if (0 == isSavepnt && pager_cksum(aData) != cksum) { return(RC.DONE); } } // If this page has already been played by before during the current rollback, then don't bother to play it back again. if (pDone != null && (rc = pDone.Set(pgno)) != RC.OK) { return(rc); } // When playing back page 1, restore the nReserve setting if (pgno == 1 && this.nReserve != aData[20]) { this.nReserve = (aData)[20]; pagerReportSize(); } var pPg = (pagerUseWal() ? null : pager_lookup(pgno)); // An existing page in the cache Debug.Assert(pPg != null || #if SQLITE_OMIT_MEMORYDB 0 == MEMDB #else this.memDb == 0 #endif ); Debug.Assert(this.eState != PAGER.OPEN || pPg == null); PAGERTRACE("PLAYBACK {0} page {1} hash({2,08:x}) {3}", PAGERID(this), pgno, pager_datahash(this.pageSize, aData), isMainJrnl != 0 ? "main-journal" : "sub-journal"); bool isSynced; // True if journal page is synced if (isMainJrnl != 0) { isSynced = this.noSync || (pOffset <= this.journalHdr); } else { isSynced = (pPg == null || 0 == (pPg.Flags & PgHdr.PGHDR.NEED_SYNC)); } if (this.fd.IsOpen && (this.eState >= PAGER.WRITER_DBMOD || this.eState == PAGER.OPEN) && isSynced) { long ofst = (pgno - 1) * this.pageSize; Debug.Assert(!pagerUseWal()); rc = this.fd.Write(aData, this.pageSize, ofst); if (pgno > this.dbFileSize) { this.dbFileSize = pgno; } if (this.pBackup != null) { if (CODEC1(this, aData, pgno, codec_ctx.DECRYPT)) { rc = RC.NOMEM; } this.pBackup.sqlite3BackupUpdate(pgno, aData); if (CODEC2(this, aData, pgno, codec_ctx.ENCRYPT_READ_CTX, ref aData)) { rc = RC.NOMEM; } } } else if (0 == isMainJrnl && pPg == null) { // If this is a rollback of a savepoint and data was not written to the database and the page is not in-memory, there is a potential // problem. When the page is next fetched by the b-tree layer, it will be read from the database file, which may or may not be // current. // There are a couple of different ways this can happen. All are quite obscure. When running in synchronous mode, this can only happen // if the page is on the free-list at the start of the transaction, then populated, then moved using sqlite3PagerMovepage(). // The solution is to add an in-memory page to the cache containing the data just read from the sub-journal. Mark the page as dirty // and if the pager requires a journal-sync, then mark the page as requiring a journal-sync before it is written. Debug.Assert(isSavepnt != 0); Debug.Assert(this.doNotSpill == 0); this.doNotSpill++; rc = this.Get(pgno, ref pPg, 1); Debug.Assert(this.doNotSpill == 1); this.doNotSpill--; if (rc != RC.OK) { return(rc); } pPg.Flags &= ~PgHdr.PGHDR.NEED_READ; PCache.MakePageDirty(pPg); } if (pPg != null) { // No page should ever be explicitly rolled back that is in use, except for page 1 which is held in use in order to keep the lock on the // database active. However such a page may be rolled back as a result of an internal error resulting in an automatic call to // sqlite3PagerRollback(). var pData = pPg.Data; Buffer.BlockCopy(aData, 0, pData, 0, this.pageSize); this.xReiniter(pPg); if (isMainJrnl != 0 && (0 == isSavepnt || pOffset <= this.journalHdr)) { // If the contents of this page were just restored from the main journal file, then its content must be as they were when the // transaction was first opened. In this case we can mark the page as clean, since there will be no need to write it out to the // database. // There is one exception to this rule. If the page is being rolled back as part of a savepoint (or statement) rollback from an // unsynced portion of the main journal file, then it is not safe to mark the page as clean. This is because marking the page as // clean will clear the PGHDR_NEED_SYNC flag. Since the page is already in the journal file (recorded in Pager.pInJournal) and // the PGHDR_NEED_SYNC flag is cleared, if the page is written to again within this transaction, it will be marked as dirty but // the PGHDR_NEED_SYNC flag will not be set. It could then potentially be written out into the database file before its journal file // segment is synced. If a crash occurs during or following this, database corruption may ensue. Debug.Assert(!pagerUseWal()); PCache.MakePageClean(pPg); } pager_set_pagehash(pPg); // If this was page 1, then restore the value of Pager.dbFileVers. Do this before any decoding. if (pgno == 1) { Buffer.BlockCopy(pData, 24, this.dbFileVers, 0, this.dbFileVers.Length); } // Decode the page just read from disk if (CODEC1(this, pData, pPg.ID, codec_ctx.DECRYPT)) { rc = RC.NOMEM; } PCache.ReleasePage(pPg); } return(rc); }
// was:sqlite3PcacheOpen internal static void Open(int szPage, int szExtra, bool bPurgeable, Func<object, PgHdr, RC> xStress, object pStress, PCache p) { p.ClearState(); p.szPage = szPage; p.szExtra = szExtra; p.bPurgeable = bPurgeable; p.xStress = xStress; p.pStress = pStress; p.nMax = 100; }
// was:sqlite3PcacheOpen internal static void Open(int szPage, int szExtra, bool bPurgeable, Func <object, PgHdr, RC> xStress, object pStress, PCache p) { p.ClearState(); p.szPage = szPage; p.szExtra = szExtra; p.bPurgeable = bPurgeable; p.xStress = xStress; p.pStress = pStress; p.nMax = 100; }
private static RC pagerStress(object p, PgHdr pPg) { var pPager = (Pager)p; var rc = RC.OK; Debug.Assert(pPg.Pager == pPager); Debug.Assert((pPg.Flags & PgHdr.PGHDR.DIRTY) != 0); // The doNotSyncSpill flag is set during times when doing a sync of journal (and adding a new header) is not allowed. This occurs // during calls to sqlite3PagerWrite() while trying to journal multiple pages belonging to the same sector. // The doNotSpill flag inhibits all cache spilling regardless of whether or not a sync is required. This is set during a rollback. // Spilling is also prohibited when in an error state since that could lead to database corruption. In the current implementaton it // is impossible for sqlite3PCacheFetch() to be called with createFlag==1 while in the error state, hence it is impossible for this routine to // be called in the error state. Nevertheless, we include a NEVER() test for the error state as a safeguard against future changes. if (Check.NEVER(pPager.errCode != 0)) { return(RC.OK); } if (pPager.doNotSpill != 0) { return(RC.OK); } if (pPager.doNotSyncSpill != 0 && (pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0) { return(RC.OK); } pPg.Dirtys = null; if (pPager.pagerUseWal()) { // Write a single frame for this page to the log. if (subjRequiresPage(pPg)) { rc = subjournalPage(pPg); } if (rc == RC.OK) { rc = pPager.pagerWalFrames(pPg, 0, 0, 0); } } else { // Sync the journal file if required. if ((pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0 || pPager.eState == PAGER.WRITER_CACHEMOD) { rc = pPager.syncJournal(1); } // If the page number of this page is larger than the current size of the database image, it may need to be written to the sub-journal. // This is because the call to pager_write_pagelist() below will not actually write data to the file in this case. // Consider the following sequence of events: // BEGIN; // <journal page X> // <modify page X> // SAVEPOINT sp; // <shrink database file to Y pages> // pagerStress(page X) // ROLLBACK TO sp; // If (X>Y), then when pagerStress is called page X will not be written out to the database file, but will be dropped from the cache. Then, // following the "ROLLBACK TO sp" statement, reading page X will read data from the database file. This will be the copy of page X as it // was when the transaction started, not as it was when "SAVEPOINT sp" was executed. // The solution is to write the current data for page X into the sub-journal file now (if it is not already there), so that it will // be restored to its current value when the "ROLLBACK TO sp" is executed. if (Check.NEVER(rc == RC.OK && pPg.ID > pPager.dbSize && subjRequiresPage(pPg))) { rc = subjournalPage(pPg); } // Write the contents of the page out to the database file. if (rc == RC.OK) { Debug.Assert((pPg.Flags & PgHdr.PGHDR.NEED_SYNC) == 0); rc = pPager.pager_write_pagelist(pPg); } } // Mark the page as clean. if (rc == RC.OK) { PAGERTRACE("STRESS {0} page {1}", PAGERID(pPager), pPg.ID); PCache.MakePageClean(pPg); } return(pPager.pager_error(rc)); }