From d41afdd4062bc5b1116c0440a6531f3fc28833b5 Mon Sep 17 00:00:00 2001
From: Oleksii Datsiuk <58850773+oleksii-datsiuk@users.noreply.github.com>
Date: Mon, 10 Jun 2024 15:42:19 +0300
Subject: [PATCH 1/3] Fix ENSURE: empty page must be defined as empty type
---
LiteDB/Engine/Services/SnapShot.cs | 18 ++++----
LiteDB/Engine/Services/TransactionService.cs | 45 ++++++++++----------
2 files changed, 31 insertions(+), 32 deletions(-)
diff --git a/LiteDB/Engine/Services/SnapShot.cs b/LiteDB/Engine/Services/SnapShot.cs
index f8deaeba7..9d37a5667 100644
--- a/LiteDB/Engine/Services/SnapShot.cs
+++ b/LiteDB/Engine/Services/SnapShot.cs
@@ -159,18 +159,18 @@ public void Dispose()
#region Page Version functions
///
- /// Get a a valid page for this snapshot (must consider local-index and wal-index)
+ /// Get a valid page for this snapshot (must consider local-index and wal-index)
///
- public T GetPage(uint pageID)
+ public T GetPage(uint pageID, bool useLatestVersion = false)
where T : BasePage
{
- return this.GetPage(pageID, out var origin, out var position, out var walVersion);
+ return this.GetPage(pageID, out var origin, out var position, out var walVersion, useLatestVersion);
}
///
- /// Get a a valid page for this snapshot (must consider local-index and wal-index)
+ /// Get a valid page for this snapshot (must consider local-index and wal-index)
///
- public T GetPage(uint pageID, out FileOrigin origin, out long position, out int walVersion)
+ public T GetPage(uint pageID, out FileOrigin origin, out long position, out int walVersion, bool useLatestVersion = false)
where T : BasePage
{
ENSURE(!_disposed, "the snapshot is disposed");
@@ -198,7 +198,7 @@ public T GetPage(uint pageID, out FileOrigin origin, out long position, out i
}
// if page is not in local cache, get from disk (log/wal/data)
- page = this.ReadPage(pageID, out origin, out position, out walVersion);
+ page = this.ReadPage(pageID, out origin, out position, out walVersion, useLatestVersion);
// add into local pages
_localPages[pageID] = page;
@@ -212,7 +212,7 @@ public T GetPage(uint pageID, out FileOrigin origin, out long position, out i
///
/// Read page from disk (dirty, wal or data)
///
- private T ReadPage(uint pageID, out FileOrigin origin, out long position, out int walVersion)
+ private T ReadPage(uint pageID, out FileOrigin origin, out long position, out int walVersion, bool useLatestVersion = false)
where T : BasePage
{
// if not inside local pages can be a dirty page saved in log file
@@ -232,7 +232,7 @@ private T ReadPage(uint pageID, out FileOrigin origin, out long position, out
}
// now, look inside wal-index
- var pos = _walIndex.GetPageIndex(pageID, _readVersion, out walVersion);
+ var pos = _walIndex.GetPageIndex(pageID, useLatestVersion ? int.MaxValue : _readVersion, out walVersion);
if (pos != long.MaxValue)
{
@@ -352,7 +352,7 @@ public T NewPage()
// try get page from Empty free list
if (_header.FreeEmptyPageList != uint.MaxValue)
{
- var free = this.GetPage(_header.FreeEmptyPageList);
+ var free = this.GetPage(_header.FreeEmptyPageList, useLatestVersion: true);
ENSURE(free.PageType == PageType.Empty, "empty page must be defined as empty type");
diff --git a/LiteDB/Engine/Services/TransactionService.cs b/LiteDB/Engine/Services/TransactionService.cs
index 31c2a6e42..b01b40c18 100644
--- a/LiteDB/Engine/Services/TransactionService.cs
+++ b/LiteDB/Engine/Services/TransactionService.cs
@@ -205,29 +205,25 @@ IEnumerable source()
// 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;
+ // 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;
+ // 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);
+ // invoke all header callbacks (new/drop collections)
+ _transPages.OnCommit(_header);
- // clone header page
- var buffer = _header.UpdateBuffer();
- var clone = _disk.NewPage();
+ // 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);
+ // 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;
- }
+ // 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
@@ -256,13 +252,16 @@ public void Commit()
if (_mode == LockMode.Write || _transPages.HeaderChanged)
{
- // persist all dirty page as commit mode (mark last page as IsConfirm)
- var count = this.PersistDirtyPages(true);
-
- // update wal-index (if any page was added into log disk)
- if(count > 0)
+ lock (_header)
{
- _walIndex.ConfirmTransaction(_transactionID, _transPages.DirtyPages.Values);
+ // persist all dirty page as commit mode (mark last page as IsConfirm)
+ var count = this.PersistDirtyPages(true);
+
+ // update wal-index (if any page was added into log disk)
+ if (count > 0)
+ {
+ _walIndex.ConfirmTransaction(_transactionID, _transPages.DirtyPages.Values);
+ }
}
}
From 142376ea1cf560ebf41a4023718df2d3f85796d9 Mon Sep 17 00:00:00 2001
From: Oleksii Datsiuk <58850773+oleksii-datsiuk@users.noreply.github.com>
Date: Mon, 10 Jun 2024 15:44:33 +0300
Subject: [PATCH 2/3] Add savepoint for header where it seems to be intended
(according to comment in the code) but minimize scope of the code affected by
savepoint
---
LiteDB/Engine/Services/SnapShot.cs | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/LiteDB/Engine/Services/SnapShot.cs b/LiteDB/Engine/Services/SnapShot.cs
index 9d37a5667..f34840e2f 100644
--- a/LiteDB/Engine/Services/SnapShot.cs
+++ b/LiteDB/Engine/Services/SnapShot.cs
@@ -375,11 +375,21 @@ public T NewPage()
if (newLength > _header.Pragmas.LimitSize) throw new LiteException(0, $"Maximum data file size has been reached: {FileHelper.FormatFileSize(_header.Pragmas.LimitSize)}");
- // increase LastPageID from shared page
- pageID = ++_header.LastPageID;
-
- // request for a new buffer
- buffer = _reader.NewPage();
+ var savepoint = _header.Savepoint();
+ try
+ {
+ // increase LastPageID from shared page
+ pageID = ++_header.LastPageID;
+
+ // request for a new buffer
+ buffer = _reader.NewPage();
+ }
+ catch
+ {
+ // must revert all header content if any error occurs during header change
+ _header.Restore(savepoint);
+ throw;
+ }
}
// retain a list of created pages to, in a rollback situation, back pages to empty list
From 73f586e3ab4e2565edb1951e05f55685af5136c0 Mon Sep 17 00:00:00 2001
From: Oleksii Datsiuk <58850773+oleksii-datsiuk@users.noreply.github.com>
Date: Mon, 10 Jun 2024 15:48:28 +0300
Subject: [PATCH 3/3] Make WalIndexService.CurrentReadVersion property
synchronized with other functions that modify its backing field
---
LiteDB/Engine/Services/WalIndexService.cs | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/LiteDB/Engine/Services/WalIndexService.cs b/LiteDB/Engine/Services/WalIndexService.cs
index b17d9da92..eaccc1fe2 100644
--- a/LiteDB/Engine/Services/WalIndexService.cs
+++ b/LiteDB/Engine/Services/WalIndexService.cs
@@ -38,7 +38,22 @@ public WalIndexService(DiskService disk, LockService locker)
///
/// Get current read version for all new transactions
///
- public int CurrentReadVersion => _currentReadVersion;
+ public int CurrentReadVersion
+ {
+ get
+ {
+ _indexLock.TryEnterReadLock(-1);
+
+ try
+ {
+ return _currentReadVersion;
+ }
+ finally
+ {
+ _indexLock.ExitReadLock();
+ }
+ }
+ }
///
/// Get current counter for transaction ID