/// <summary> /// Set a page as dirty and ensure page are in cache. Should be used after any change on page /// Do not use on end of method because page can be deleted/change type /// Always remove from clean list and add in dirty list /// [ThreadSafe] /// </summary> public void SetDirty(BasePage page) { _clean.Remove(page.PageID); page.IsDirty = true; _dirty[page.PageID] = page; }
/// <summary> /// Save all dirty pages to disk /// </summary> public void PersistDirtyPages() { // get header page var header = _pager.GetPage <HeaderPage>(0); // increase file changeID (back to 0 when overflow) header.ChangeID = header.ChangeID == ushort.MaxValue ? (ushort)0 : (ushort)(header.ChangeID + (ushort)1); // mark header as dirty _pager.SetDirty(header); _log.Write(Logger.DISK, "begin disk operations - changeID: {0}", header.ChangeID); // write journal file in desc order to header be last page in disk if (_disk.IsJournalEnabled) { _disk.WriteJournal(_cache.GetDirtyPages() .OrderByDescending(x => x.PageID) .Select(x => x.DiskData) .Where(x => x.Length > 0) .ToList(), header.LastPageID); // mark header as recovery before start writing (in journal, must keep recovery = false) header.Recovery = true; // flush to disk to ensure journal is committed to disk before proceeding _disk.Flush(); } else { // if no journal extend, resize file here to fast writes _disk.SetLength(BasePage.GetSizeOfPages(header.LastPageID + 1)); } // write header page first. if header.Recovery == true, this ensures it's written to disk *before* we start changing pages var headerPage = _cache.GetPage(0); var headerBuffer = headerPage.WritePage(); _disk.WritePage(0, headerBuffer); _disk.Flush(); // get all dirty page stating from Header page (SortedList) // header page (id=0) always must be first page to write on disk because it's will mark disk as "in recovery" foreach (var page in _cache.GetDirtyPages()) { // we've already written the header, so skip it if (page.PageID == 0) { continue; } // page.WritePage() updated DiskData with new rendered buffer var buffer = _crypto == null || page.PageID == 0 ? page.WritePage() : _crypto.Encrypt(page.WritePage()); _disk.WritePage(page.PageID, buffer); } if (_disk.IsJournalEnabled) { // ensure changed pages are persisted to disk *before* we change header.Recovery to false _disk.Flush(); // re-write header page but now with recovery=false header.Recovery = false; _log.Write(Logger.DISK, "re-write header page now with recovery = false"); _disk.WritePage(0, header.WritePage()); } // mark all dirty pages as clean pages (all are persisted in disk and are valid pages) _cache.MarkDirtyAsClean(); // flush all data direct to disk _disk.Flush(); // discard journal file _disk.ClearJournal(header.LastPageID); }
/// <summary> /// Shrink datafile to crop journal area /// </summary> public void ClearJournal(uint lastPageID) { _log.Write(Logger.JOURNAL, "shrink datafile to remove journal area"); this.SetLength(BasePage.GetSizeOfPages(lastPageID + 1)); }