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); }
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)); }