Exemple #1
0
        // 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);
            }
        }
Exemple #2
0
        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;
        }
Exemple #3
0
 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;
 }
Exemple #4
0
 // was:sqlite3PagerPageRefcount
 public static int GetPageRefCount(DbPage pPage)
 {
     return(PCache.sqlite3PcachePageRefcount(pPage));
 }
Exemple #5
0
 // was:sqlite3PagerRef
 public static void AddPageRef(DbPage pPg)
 {
     PCache.AddPageRef(pPg);
 }
Exemple #6
0
        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);
        }
Exemple #7
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);
        }
 // 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;
 }
Exemple #9
0
 // 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;
 }
Exemple #10
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));
        }