/// <summary> /// Get a page from cache or from disk (get from cache or from disk) /// </summary> public T GetPage <T>(uint pageID) where T : BasePage { lock (_disk) { var page = _cache.GetPage(pageID); // is not on cache? load from disk if (page == null) { var buffer = _disk.ReadPage(pageID); // if datafile are encrypted, decrypt buffer (header are not encrypted) if (_crypto != null && pageID > 0) { buffer = _crypto.Decrypt(buffer); } page = BasePage.ReadPage(buffer); _cache.AddPage(page); } return((T)page); } }
private void AvoidDirtyRead() { // if disk are exclusive don't need check dirty read if (_disk.IsExclusive) { return; } _log.Write(Logger.CACHE, "checking disk to avoid dirty read"); // empty cache? just exit if (_cache.CleanUsed == 0) { return; } // get ChangeID from cache var header = _cache.GetPage(0) as HeaderPage; var changeID = header == null ? 0 : header.ChangeID; // and get header from disk var disk = BasePage.ReadPage(_disk.ReadPage(0)) as HeaderPage; // if header change, clear cache and add new header to cache if (disk.ChangeID != changeID) { _log.Write(Logger.CACHE, "file changed from another process, cleaning all cache pages"); _cache.ClearPages(); _cache.AddPage(disk); } }
/// <summary> /// Get a page from cache or from disk (and put on cache) /// </summary> public T GetPage <T>(uint pageID, bool setDirty = false) where T : BasePage { var page = _cache.GetPage(pageID); // is not on cache? load from disk if (page == null) { var buffer = _disk.ReadPage(pageID); page = BasePage.ReadPage(buffer); _cache.AddPage(page); } #if DEBUG // if page is empty, convert to T if (page.PageType == PageType.Empty && typeof(T) != typeof(BasePage)) { throw new Exception("Pager.GetPage<T>() never shuld happend"); } #endif // set page as dirty if passing by param if (setDirty) { this.SetDirty((T)page); } return((T)page); }
/// <summary> /// Get a page from cache or from disk (and put on cache) /// </summary> public T GetPage <T>(uint pageID) where T : BasePage, new() { var page = _cache.GetPage <T>(pageID); if (page == null) { page = _disk.ReadPage <T>(pageID); _cache.AddPage(page); } return(page); }
/// <summary> /// Test if cache still valid (if datafile was changed by another process reset cache) /// Returns true if file was changed /// [Thread Safe] /// </summary> private bool DetectDatabaseChanges() { // if disk are exclusive don't need check dirty read if (_disk.IsExclusive) { return(false); } // empty cache? just exit if (_cache.CleanUsed == 0) { return(false); } _log.Write(Logger.CACHE, "checking disk to detect database changes from another process"); // get ChangeID from cache var header = _cache.GetPage(0) as HeaderPage; var changeID = header == null ? 0 : header.ChangeID; // and get header from disk var disk = BasePage.ReadPage(_disk.ReadPage(0)) as HeaderPage; // if disk header are in recovery mode, throw exception to datafile re-open and recovery pages if (disk.Recovery) { _log.Write(Logger.ERROR, "datafile in recovery mode, need re-open database"); throw LiteException.NeedRecover(); } // if header change, clear cache and add new header to cache if (disk.ChangeID != changeID) { _log.Write(Logger.CACHE, "file changed from another process, cleaning all cache pages"); _cache.ClearPages(); _cache.AddPage(disk); return(true); } return(false); }
/// <summary> /// This method must be called before read/write operation to avoid dirty reads. /// It's occurs when my cache contains pages that was changed in another process /// </summary> public bool AvoidDirtyRead() { var cache = (HeaderPage)_cache.GetPage(0); if (cache == null) { return(false); } // read change direct from disk var change = _disk.GetChangeID(); // if changeID was changed, file was changed by another process - clear all cache if (cache.ChangeID != change) { _cache.Clear(); return(true); } return(false); }
/// <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); }