/// <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); }
/// <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; // 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 (only if a real commit and no header changes) if (page.IsLast) { page.Item.IsConfirmed = markLastAsConfirmed; } // if current page is last deleted page, point this page to last free if (_transPages.LastDeletedPageID == page.Item.PageID && commit) { ENSURE(_transPages.HeaderChanged, "must header be in lock"); ENSURE(page.Item.PageType == PageType.Empty, "must be marked as deleted page"); // join existing free list pages into new list of deleted pages page.Item.NextPageID = _header.FreeEmptyPageList; // and now, set header free list page to this new list _header.FreeEmptyPageList = _transPages.FirstDeletedPageID; } 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); } // in commit with header page change, last page will be header if (commit && _transPages.HeaderChanged) { lock (_header) { // update this confirm page with current transactionID _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.DiscardCleanPages(_snapshots.Values .Where(x => x.Mode == LockMode.Write) .SelectMany(x => x.GetWritablePages(false, commit)) .Select(x => x.Buffer)); return(count); }