diff --git a/src/ZoneTree.UnitTests/IteratorTests.cs b/src/ZoneTree.UnitTests/IteratorTests.cs index e8e32df..153d853 100644 --- a/src/ZoneTree.UnitTests/IteratorTests.cs +++ b/src/ZoneTree.UnitTests/IteratorTests.cs @@ -1,4 +1,5 @@ -using Tenray.ZoneTree.Comparers; +using Tenray.ZoneTree.Collections.BplusTree.Lock; +using Tenray.ZoneTree.Comparers; using Tenray.ZoneTree.Serializers; namespace Tenray.ZoneTree.UnitTests; @@ -289,16 +290,16 @@ public class IteratorTests zoneTree.Maintenance.DestroyTree(); } - - [Test] - public void IntIntReverseIteratorParallelInserts() + [TestCase(true)] + [TestCase(false)] + public void IntIntReverseIteratorParallelInserts(bool reverse) { var dataPath = "data/IntIntReverseIteratorParallelInserts"; if (Directory.Exists(dataPath)) Directory.Delete(dataPath, true); var random = new Random(); - var insertCount = 100000; + var insertCount = 1000000; var iteratorCount = 1000; using var zoneTree = new ZoneTreeFactory() @@ -308,36 +309,45 @@ public class IteratorTests .SetWriteAheadLogDirectory(dataPath) .SetKeySerializer(new Int32Serializer()) .SetValueSerializer(new Int32Serializer()) + .Configure(x => + { + x.BTreeLockMode = BTreeLockMode.NodeLevelMonitor; + }) .OpenOrCreate(); - var task = Task.Factory.StartNew(() => + var task = Task.Run(() => { Parallel.For(0, insertCount, (x) => { - var key = random.Next(); + var key = random.Next(0, 100_000); zoneTree.Upsert(key, key + key); }); }); Parallel.For(0, iteratorCount, (x) => { - var initialCount = zoneTree.Maintenance.InMemoryRecordCount; - using var iterator = zoneTree.CreateReverseIterator(false); + var initialCount = zoneTree.Maintenance.MutableSegmentRecordCount; + using var iterator = + reverse ? + zoneTree.CreateReverseIterator(false) : + zoneTree.CreateIterator(false); iterator.SeekFirst(); var counter = 1; var isValidData = true; - var previousKey = int.MaxValue; + var previousKey = reverse ? int.MaxValue : int.MinValue; while (iterator.Next()) { var expected = iterator.CurrentKey + iterator.CurrentKey; if (iterator.CurrentValue != expected) isValidData = false; - if (iterator.CurrentKey >= previousKey) + if (reverse && iterator.CurrentKey >= previousKey) + throw new Exception("Reverse Iterator is not iterating in valid order."); + + if (!reverse && iterator.CurrentKey <= previousKey) throw new Exception("Iterator is not iterating in valid order."); + previousKey = iterator.CurrentKey; ++counter; } - if (counter < initialCount) - Console.WriteLine(initialCount + " > " + counter); Assert.That(counter, Is.GreaterThanOrEqualTo(initialCount)); Assert.That(isValidData, Is.True); }); diff --git a/src/ZoneTree.UnitTests/SafeBplusTreeTests.cs b/src/ZoneTree.UnitTests/SafeBplusTreeTests.cs index de6a2d9..8d52361 100644 --- a/src/ZoneTree.UnitTests/SafeBplusTreeTests.cs +++ b/src/ZoneTree.UnitTests/SafeBplusTreeTests.cs @@ -245,4 +245,92 @@ public class SafeBTreeTests task.Wait(); } + + [TestCase(BTreeLockMode.TopLevelReaderWriter)] + [TestCase(BTreeLockMode.TopLevelMonitor)] + [TestCase(BTreeLockMode.NodeLevelReaderWriter)] + [TestCase(BTreeLockMode.NodeLevelMonitor)] + public void IntIntDuplicateRecords(BTreeLockMode lockMode) + { + var random = new Random(); + var insertCount = 1000000; + var iteratorCount = 1000; + + var tree = new BTree(new Int32ComparerAscending(), lockMode); + var task = Task.Run(() => + { + Parallel.For(0, insertCount, (x) => + { + var key = random.Next(0, 100000); + tree.Upsert(key, key + key, out _); + }); + }); + + Parallel.For(0, iteratorCount, (x) => + { + var initialCount = tree.Length; + var iterator = new BTreeSeekableIterator(tree); + var counter = iterator.SeekBegin() ? 1 : 0; + var isValidData = true; + var previousKey = int.MinValue; + while (iterator.Next()) + { + var expected = iterator.CurrentKey + iterator.CurrentKey; + if (iterator.CurrentValue != expected) + isValidData = false; + if (iterator.CurrentKey <= previousKey) + throw new Exception( + $"Iterator is not iterating in valid order.{iterator.CurrentKey} <= {previousKey}"); + previousKey = iterator.CurrentKey; + ++counter; + } + Assert.That(counter, Is.GreaterThanOrEqualTo(initialCount)); + Assert.That(isValidData, Is.True); + }); + task.Wait(); + } + + [TestCase(BTreeLockMode.TopLevelReaderWriter)] + [TestCase(BTreeLockMode.TopLevelMonitor)] + [TestCase(BTreeLockMode.NodeLevelReaderWriter)] + [TestCase(BTreeLockMode.NodeLevelMonitor)] + public void IntIntDuplicateReverseRecords(BTreeLockMode lockMode) + { + var random = new Random(); + var insertCount = 1000000; + var iteratorCount = 1000; + + var tree = new BTree(new Int32ComparerAscending(), lockMode); + var task = Task.Run(() => + { + Parallel.For(0, insertCount, (x) => + { + var key = random.Next(0, 100000); + tree.Upsert(key, key + key, out _); + }); + }); + + Parallel.For(0, iteratorCount, (x) => + { + var initialCount = tree.Length; + var iterator = new BTreeSeekableIterator(tree); + var counter = iterator.SeekEnd() ? 1 : 0; + var isValidData = true; + var previousKey = int.MaxValue; + while (iterator.Prev()) + { + var expected = iterator.CurrentKey + iterator.CurrentKey; + if (iterator.CurrentValue != expected) + isValidData = false; + if (iterator.CurrentKey >= previousKey) + throw new Exception( + $"Reverse Iterator is not iterating in valid order.{iterator.CurrentKey} >= {previousKey}"); + previousKey = iterator.CurrentKey; + ++counter; + } + Assert.That(counter, Is.GreaterThanOrEqualTo(initialCount)); + Assert.That(isValidData, Is.True); + }); + task.Wait(); + } } diff --git a/src/ZoneTree/Collections/BplusTree/BTree.Write.cs b/src/ZoneTree/Collections/BplusTree/BTree.Write.cs index b81284c..a6daed9 100644 --- a/src/ZoneTree/Collections/BplusTree/BTree.Write.cs +++ b/src/ZoneTree/Collections/BplusTree/BTree.Write.cs @@ -200,7 +200,8 @@ public partial class BTree node.WriteUnlock(); return true; } - + if (found) + ++position; var child = node.Children[position]; child.WriteLock(); if (child.IsFull) @@ -208,7 +209,7 @@ public partial class BTree SplitChild(node, position, child); child.WriteUnlock(); - if (Comparer.Compare(in key, in node.Keys[position]) > 0) + if (Comparer.Compare(in key, in node.Keys[position]) >= 0) ++position; child = node.Children[position]; @@ -240,6 +241,9 @@ public partial class BTree return true; } + if (found) + ++position; + var child = node.Children[position]; child.WriteLock(); if (child.IsFull) @@ -247,7 +251,7 @@ public partial class BTree SplitChild(node, position, child); child.WriteUnlock(); - if (Comparer.Compare(in key, in node.Keys[position]) > 0) + if (Comparer.Compare(in key, in node.Keys[position]) >= 0) ++position; child = node.Children[position]; @@ -282,6 +286,9 @@ public partial class BTree return AddOrUpdateResult.ADDED; } + if (found) + ++position; + var child = node.Children[position]; child.WriteLock(); if (child.IsFull) @@ -289,7 +296,7 @@ public partial class BTree SplitChild(node, position, child); child.WriteUnlock(); - if (Comparer.Compare(in key, in node.Keys[position]) > 0) + if (Comparer.Compare(in key, in node.Keys[position]) >= 0) ++position; child = node.Children[position];