Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #946 #950

Merged
merged 15 commits into from
Jul 30, 2019
126 changes: 126 additions & 0 deletions neo.UnitTests/UT_DataCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.IO.Caching;
using Neo.Ledger;
using System.Linq;

namespace Neo.UnitTests
{
[TestClass]
public class UT_DataCache
{
[TestInitialize]
public void TestSetup()
{
TestBlockchain.InitializeMockNeoSystem();
}

[TestMethod]
public void TestCachedFind_Between()
{
var snapshot = TestBlockchain.GetStore().GetSnapshot();
var storages = snapshot.Storages;
var cache = new CloneCache<StorageKey, StorageItem>(storages);

storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero);

storages.Add
(
new StorageKey() { Key = new byte[] { 0x01, 0x01 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
storages.Add
(
new StorageKey() { Key = new byte[] { 0x00, 0x01 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
storages.Add
(
new StorageKey() { Key = new byte[] { 0x00, 0x03 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
cache.Add
(
new StorageKey() { Key = new byte[] { 0x01, 0x02 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
cache.Add
(
new StorageKey() { Key = new byte[] { 0x00, 0x02 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);

CollectionAssert.AreEqual(
cache.Find(new byte[] { 0x00 }).Select(u => u.Key.Key[1]).ToArray(),
new byte[] { 0x01, 0x02, 0x03 }
);

storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero);
}

[TestMethod]
public void TestCachedFind_Last()
{
var snapshot = TestBlockchain.GetStore().GetSnapshot();
var storages = snapshot.Storages;
var cache = new CloneCache<StorageKey, StorageItem>(storages);

storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero);

storages.Add
(
new StorageKey() { Key = new byte[] { 0x00, 0x01 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
storages.Add
(
new StorageKey() { Key = new byte[] { 0x01, 0x01 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
cache.Add
(
new StorageKey() { Key = new byte[] { 0x00, 0x02 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
cache.Add
(
new StorageKey() { Key = new byte[] { 0x01, 0x02 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);

CollectionAssert.AreEqual(
cache.Find(new byte[] { 0x00 }).Select(u => u.Key.Key[1]).ToArray(),
new byte[] { 0x01, 0x02 }
);

storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero);
}

[TestMethod]
public void TestCachedFind_Empty()
{
var snapshot = TestBlockchain.GetStore().GetSnapshot();
var storages = snapshot.Storages;
var cache = new CloneCache<StorageKey, StorageItem>(storages);

storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero);

cache.Add
(
new StorageKey() { Key = new byte[] { 0x00, 0x02 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);
cache.Add
(
new StorageKey() { Key = new byte[] { 0x01, 0x02 }, ScriptHash = UInt160.Zero },
new StorageItem() { IsConstant = false, Value = new byte[] { } }
);

CollectionAssert.AreEqual(
cache.Find(new byte[] { 0x00 }).Select(u => u.Key.Key[1]).ToArray(),
new byte[] { 0x02 }
);

storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero);
}
}
}
21 changes: 21 additions & 0 deletions neo/IO/ByteArrayComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;

namespace Neo.IO
{
internal class ByteArrayComparer : IComparer<byte[]>
{
public static readonly ByteArrayComparer Default = new ByteArrayComparer();

public int Compare(byte[] x, byte[] y)
{
int length = Math.Min(x.Length, y.Length);
for (int i = 0; i < length; i++)
{
int r = x[i].CompareTo(y[i]);
if (r != 0) return r;
}
return x.Length.CompareTo(y.Length);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rodoufu can you review this?

61 changes: 55 additions & 6 deletions neo/IO/Caching/DataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,65 @@ public void DeleteWhere(Func<TKey, TValue, bool> predicate)
}
}

public byte[] ToByteArray(TKey x)
{
if (x is StorageKey xx)
{
return xx.Key;
}
else
{
return x.ToArray();
}
}

public IEnumerable<KeyValuePair<TKey, TValue>> Find(byte[] key_prefix = null)
{
IEnumerable<(byte[], TKey, TValue)> cached;
lock (dictionary)
{
foreach (var pair in FindInternal(key_prefix ?? new byte[0]))
if (!dictionary.ContainsKey(pair.Key))
yield return pair;
foreach (var pair in dictionary)
if (pair.Value.State != TrackState.Deleted && (key_prefix == null || pair.Key.ToArray().Take(key_prefix.Length).SequenceEqual(key_prefix)))
yield return new KeyValuePair<TKey, TValue>(pair.Key, pair.Value.Item);
cached = dictionary
.Where(p => p.Value.State != TrackState.Deleted && (key_prefix == null || ToByteArray(p.Key).Take(key_prefix.Length).SequenceEqual(key_prefix)))
.Select(p =>
(
KeyBytes: p.Key.ToArray(),
p.Key,
p.Value.Item
))
.OrderBy(p => p.KeyBytes, ByteArrayComparer.Default)
.ToArray();
}
var uncached = FindInternal(key_prefix ?? new byte[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this FindInternal implementation-specific? This is a good moment to avoid specific decisions affecting the outcome of Find.

.Where(p => !dictionary.ContainsKey(p.Key))
.Select(p =>
(
KeyBytes: ToByteArray(p.Key),
p.Key,
p.Value
));
using (var e1 = cached.GetEnumerator())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this "intercalation" strategy should be done in a separate function, so it's clear and also testable (perhaps useful in other situations too).

using (var e2 = uncached.GetEnumerator())
{
(byte[] KeyBytes, TKey Key, TValue Item) i1, i2;
bool c1 = e1.MoveNext();
bool c2 = e2.MoveNext();
i1 = c1 ? e1.Current : default;
i2 = c2 ? e2.Current : default;
while (c1 || c2)
{
if (!c2 || (c1 && ByteArrayComparer.Default.Compare(i1.KeyBytes, i2.KeyBytes) < 0))
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
yield return new KeyValuePair<TKey, TValue>(i1.Key, i1.Item);
c1 = e1.MoveNext();
i1 = c1 ? e1.Current : default;
}
else
{
yield return new KeyValuePair<TKey, TValue>(i2.Key, i2.Item);
c2 = e2.MoveNext();
i2 = c2 ? e2.Current : default;
}
}
}
}

Expand Down