/// <summary> /// Return added pages when occurs an rollback transaction (run this only in rollback). Create new transactionID and add into /// Log file all new pages as EmptyPage in a linked order - also, update SharedPage before store /// </summary> private void ReturnNewPages() { // create new transaction ID var transactionID = _walIndex.NextTransactionID(); // now lock header to update LastTransactionID/FreePageList lock (_header) { // persist all empty pages into wal-file var pagePositions = new Dictionary <uint, PagePosition>(); IEnumerable <PageBuffer> source() { // create list of empty pages with forward link pointer for (var i = 0; i < _transPages.NewPages.Count; i++) { var pageID = _transPages.NewPages[i]; var next = i < _transPages.NewPages.Count - 1 ? _transPages.NewPages[i + 1] : _header.FreeEmptyPageID; var buffer = _disk.NewPage(); var page = new BasePage(buffer, pageID, PageType.Empty) { NextPageID = next, TransactionID = transactionID }; yield return(page.UpdateBuffer()); // update wal pagePositions[pageID] = new PagePosition(pageID, buffer.Position); if (_disposed) { yield break; } } // update header page with my new transaction ID _header.TransactionID = transactionID; _header.FreeEmptyPageID = _transPages.NewPages[0]; _header.IsConfirmed = true; // clone header buffer var buf = _header.UpdateBuffer(); var clone = _disk.NewPage(); Buffer.BlockCopy(buf.Array, buf.Offset, clone.Array, clone.Offset, clone.Count); yield return(clone); }; // create a header save point before any change var safepoint = _header.Savepoint(); try { // write all pages (including new header) _disk.WriteAsync(source()); } catch { // must revert all header content if any error occurs during header change _header.Restore(safepoint); throw; } // now confirm this transaction to wal _walIndex.ConfirmTransaction(transactionID, pagePositions.Values); } }
/// <summary> /// Persist all dirty in-memory pages (in all snapshots) and clear local pages list (even clean pages) /// </summary> private int PersistDirtyPages(bool commit) { var dirty = 0; // inner method to get all dirty pages IEnumerable <PageBuffer> source() { // get all dirty pages from all write snapshots // can include (or not) collection pages // update DirtyPagesLog inside transPage for all dirty pages was write on disk var pages = _snapshots.Values .Where(x => x.Mode == LockMode.Write) .SelectMany(x => x.GetWritablePages(true, commit)); // mark last dirty page as confirmed only if there is no header change in commit var markLastAsConfirmed = commit && _transPages.HeaderChanged == false; // loop across all dirty pages if (markLastAsConfirmed) { // neet use "IsLast" method to get when loop are last item foreach (var page in pages.IsLast()) { // update page transactionID page.Item.TransactionID = _transactionID; // if last page, mask as confirm if (page.IsLast) { page.Item.IsConfirmed = true; } var buffer = page.Item.UpdateBuffer(); // buffer position will be set at end of file (it´s always log file) yield return(buffer); dirty++; _transPages.DirtyPages[page.Item.PageID] = new PagePosition(page.Item.PageID, buffer.Position); } } else { // avoid use "IsLast" when was not needed (better performance) foreach (var page in pages) { // update page transactionID page.TransactionID = _transactionID; var buffer = page.UpdateBuffer(); // buffer position will be set at end of file (it´s always log file) yield return(buffer); dirty++; _transPages.DirtyPages[page.PageID] = new PagePosition(page.PageID, buffer.Position); } } // in commit with header page change, last page will be header if (commit && _transPages.HeaderChanged) { // lock header page to avoid concurrency when writing on header lock (_header) { var newEmptyPageID = _header.FreeEmptyPageID; // if has deleted pages in this transaction, fix FreeEmptyPageID if (_transPages.DeletedPages > 0) { // now, my free list will starts with first page ID newEmptyPageID = _transPages.FirstDeletedPageID; // if free empty list was not empty, let's fix my last page if (_header.FreeEmptyPageID != uint.MaxValue) { var empty = _disk.NewPage(); // to avoid read a empty page from disk I will create new page as empty and override it var lastDeletedPage = new BasePage(empty, _transPages.LastDeletedPageID, PageType.Empty) { // update nextPageID of last deleted page to old first page ID NextPageID = _header.FreeEmptyPageID, TransactionID = _transactionID }; // this page will write twice on wal, but no problem, only this last version will be saved on data file yield return(lastDeletedPage.UpdateBuffer()); } } // update this confirm page with current transactionID _header.FreeEmptyPageID = newEmptyPageID; _header.TransactionID = _transactionID; // this header page will be marked as confirmed page in log file _header.IsConfirmed = true; // invoke all header callbacks (new/drop collections) _transPages.OnCommit(_header); // clone header page var buffer = _header.UpdateBuffer(); var clone = _disk.NewPage(); // mem copy from current header to new header clone Buffer.BlockCopy(buffer.Array, buffer.Offset, clone.Array, clone.Offset, clone.Count); // persist header in log file yield return(clone); } } }; // write all dirty pages, in sequence on log-file and store references into log pages on transPages // (works only for Write snapshots) var count = _disk.WriteAsync(source()); // now, discard all clean pages (because those pages are writable and must be readable) // from write snapshots _disk.DiscardPages(_snapshots.Values .Where(x => x.Mode == LockMode.Write) .SelectMany(x => x.GetWritablePages(false, commit)) .Select(x => x.Buffer), false); return(count); }