-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Fixes #946 #950
Changes from all commits
99059aa
ad67223
2dfac18
8f689e1
8ac83c6
06349e3
f2606e1
ac85174
50c51da
41ce918
12570f9
d2ccbaa
875407d
68ab63a
ca51799
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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[21]).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[21]).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[21]).Select(u => u.Key.Key[1]).ToArray(), | ||
new byte[] { 0x02 } | ||
); | ||
|
||
storages.DeleteWhere((k, v) => k.ScriptHash == UInt160.Zero); | ||
} | ||
} | ||
} |
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); | ||
} | ||
} | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -118,16 +118,58 @@ public void DeleteWhere(Func<TKey, TValue, bool> predicate) | |
} | ||
} | ||
|
||
/// <summary> | ||
/// Find the entries that start with the `key_prefix` | ||
/// </summary> | ||
/// <param name="key_prefix">Must maintain the deserialized format of TKey</param> | ||
/// <returns>Entries found with the desired prefix</returns> | ||
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 || p.Key.ToArray().Take(key_prefix.Length).SequenceEqual(key_prefix))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good to me, must do it in a deserialized format, to avoid different storage formats affecting Find. |
||
.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]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: p.Key.ToArray(), | ||
p.Key, | ||
p.Value | ||
)); | ||
using (var e1 = cached.GetEnumerator()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,17 +92,16 @@ public static byte[] ReadBytesWithGrouping(this BinaryReader reader) | |
{ | ||
using (MemoryStream ms = new MemoryStream()) | ||
{ | ||
int padding = 0; | ||
int count; | ||
do | ||
{ | ||
byte[] group = reader.ReadBytes(GroupingSizeInBytes); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should reuse this buffer
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. read.Read(group,0,GroupSizeInBytes); This may read data less than |
||
padding = reader.ReadByte(); | ||
if (padding > GroupingSizeInBytes) | ||
count = reader.ReadByte(); | ||
if (count > GroupingSizeInBytes) | ||
throw new FormatException(); | ||
int count = GroupingSizeInBytes - padding; | ||
if (count > 0) | ||
ms.Write(group, 0, count); | ||
} while (padding == 0); | ||
} while (count == GroupingSizeInBytes); | ||
return ms.ToArray(); | ||
} | ||
} | ||
|
@@ -200,7 +199,7 @@ public static void WriteBytesWithGrouping(this BinaryWriter writer, byte[] value | |
while (remain >= GroupingSizeInBytes) | ||
{ | ||
writer.Write(value, index, GroupingSizeInBytes); | ||
writer.Write((byte)0); | ||
writer.Write((byte)GroupingSizeInBytes); | ||
index += GroupingSizeInBytes; | ||
remain -= GroupingSizeInBytes; | ||
} | ||
|
@@ -209,7 +208,7 @@ public static void WriteBytesWithGrouping(this BinaryWriter writer, byte[] value | |
int padding = GroupingSizeInBytes - remain; | ||
for (int i = 0; i < padding; i++) | ||
writer.Write((byte)0); | ||
writer.Write((byte)padding); | ||
writer.Write((byte)remain); | ||
} | ||
|
||
public static void WriteFixedString(this BinaryWriter writer, string value, int length) | ||
|
There was a problem hiding this comment.
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?