2

一段时间以来,我一直在使用B Plus Tree 的这种实现。我注意到“最近”缓存有问题。以下是该错误的产生方式:

  1. 我添加了一些 KVP,并提交了树。
  2. 我添加了更多的 KVP 并回滚了树。
  3. 我添加了更多的 KVP 并提交了树。
  4. 我重新启动我的应用程序并重复步骤 1,2 和 3

树在重新启动后执行第 3 步时抛出 InvalidNodeHandleException,这正是

at CSharpTest.Net.Collections.BPlusTree`2.NodeCacheNormal.Lock(NodePin parent, LockType ltype, NodeHandle child)
at CSharpTest.Net.Collections.BPlusTree`2.RootLock..ctor(BPlusTree`2 tree, LockType type, Boolean exclusiveTreeAccess, String methodName)
at CSharpTest.Net.Collections.BPlusTree`2.LockRoot(LockType ltype, String methodName)

以下断言失败。

InvalidNodeHandleException.Assert(Storage.TryGetNode(child.StoreHandle, out node, NodeSerializer)
                                                    && node != null
                                                    && node.StorageHandle.Equals(entry.Handle.StoreHandle));

因为 StorageHandle 相等返回 false,这反过来又是因为Unique两个根存储句柄的不同,而不仅仅是增量不同,它们是两个不同的随机数。问题的根源在于NodeCacheNormal.

在第一次运行创建树之后,当树在第二次执行中第一次加载时,它是通过LoadStorage()调用完成的,它只是将 设置_root.NodeRootNode从存储中读取。此读取RootNode包含Unique提交到磁盘的最后执行时间,并且永远不会与Unique在此执行中创建的新根句柄的时间进行比较,直到树回滚。

回滚导致缓存被清除,从而导致缓存中的RootNode被清除。回滚后,如果再次访问 RootNode,则从存储中取出它并插入到缓存中,但这次除外,它通过NodePin Lock(NodePin parent, LockType ltype, NodeHandle child)检查上述调用中句柄是否相等的主调用完成。

是否有任何已知的修复方法?缓存非常适合实现,我无法找到一个好的解决方法。

编辑1:

这是一个产生错误的类:

 public class TreeBugTest
    {
        private BPlusTree<long, long> _tree;

        public TreeBugTest()
        {
            var options = new BPlusTree<long, long>.OptionsV2(new PrimitiveSerializer(), new PrimitiveSerializer());
            options.BTreeOrder = 4;
            options.CachePolicy = CachePolicy.Recent;
            options.CallLevelLock = new SimpleReadWriteLocking();
            options.FileName = ConfigurationSettings.AppSettings["Path"];
            options.CreateFile = CreatePolicy.IfNeeded;
            options.StorageType = StorageType.Disk;
            options.LockingFactory = new LockFactory<WriterOnlyLocking>();
            options.StoragePerformance = StoragePerformance.Default;
            _tree = new BPlusTree<long, long>(options);
        }

        public void AddSomeData(long start, long end)
        {
            while (start <= end)
            {
                _tree.Add(start, start++);
            }
        }

        public void ProduceBug()
        {
            AddSomeData(1, 1000);
            _tree.Commit();
            AddSomeData(1001,2000);
            _tree.Rollback();
            AddSomeData(2001, 3000); //This is where it occurs
            _tree.Commit();
        }
    }

只需提供一个文件路径,创建它的实例并调用该ProduceBug()方法。

4

1 回答 1

0

好的,显然代码有问题。对于新创建的根节点,需要跳过句柄比较。为此,我将方法的逻辑移到了带有签名Lock的私有方法中:LockInternal

private NodePin LockInternal(NodePin parent, LockType ltype, NodeHandle child, bool ignoreHandleComparison)

并更改了语句:

InvalidNodeHandleException.Assert(Storage.TryGetNode(child.StoreHandle, out node, NodeSerializer) && node != null && node.StorageHandle.Equals(entry.Handle.StoreHandle));

至:

InvalidNodeHandleException.Assert(Storage.TryGetNode(child.StoreHandle, out node, NodeSerializer) && node != null && ignoreHandleComparison?true:node.StorageHandle.Equals(entry.Handle.StoreHandle));

并使用forLockInternal从原始Lock方法调用此私有方法。我修改的第二个地方是最初的方法,我将方法设置为 virtual 并在覆盖中,我将调用从for更改为。这对我有用。falseignoreHandleComparisonLockRootNodeCacheNormalLockLockInternaltrueignoreHandleComparison

我想我会向项目的存储库提出这个修复的请求。

于 2017-01-05T10:57:24.510 回答