Exemple #1
0
        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);
        }
Exemple #2
0
        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));
        }