diff --git a/src/Enyim.Caching/FnvHash.cs b/src/Enyim.Caching/FnvHash.cs index 0481d327..2cb79926 100755 --- a/src/Enyim.Caching/FnvHash.cs +++ b/src/Enyim.Caching/FnvHash.cs @@ -23,6 +23,7 @@ public class FNV64 : System.Security.Cryptography.HashAlgorithm, IUIntHashAlgori public FNV64() { //base.HashSize = 64; + Initialize(); } /// @@ -109,6 +110,7 @@ public class FNV1 : HashAlgorithm, IUIntHashAlgorithm /// public FNV1() { + Initialize(); } /// diff --git a/src/Enyim.Caching/Memcached/Locators/DefaultNodeLocator.cs b/src/Enyim.Caching/Memcached/Locators/DefaultNodeLocator.cs index cc477a12..6fd95d04 100644 --- a/src/Enyim.Caching/Memcached/Locators/DefaultNodeLocator.cs +++ b/src/Enyim.Caching/Memcached/Locators/DefaultNodeLocator.cs @@ -172,7 +172,7 @@ private static uint[] GenerateKeys(IMemcachedNode node, int numberOfKeys) for (int i = 0; i < numberOfKeys; i++) { - byte[] data = fnv.ComputeHash(Encoding.UTF8.GetBytes(String.Concat(address, "-", i))); + byte[] data = fnv.ComputeHash(Encoding.UTF8.GetBytes(String.Concat(i, "-", address))); for (int h = 0; h < PartCount; h++) { diff --git a/test/Enyim.Caching.Tests/DefaultNodeLocatorTests.cs b/test/Enyim.Caching.Tests/DefaultNodeLocatorTests.cs new file mode 100644 index 00000000..2a990261 --- /dev/null +++ b/test/Enyim.Caching.Tests/DefaultNodeLocatorTests.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using Enyim.Caching.Memcached; +using Enyim.Caching.Memcached.Results; +using Xunit; + +namespace Enyim.Caching.Tests +{ + public class DefaultNodeLocatorTest + { + [Fact] + public void FNV1a() + { + var fnv = new Enyim.FNV1a(); + + // FNV1a test vectors: + // http://www.isthe.com/chongo/src/fnv/test_fnv.c + var testVectors = new List> + { + new Tuple("",0x811c9dc5U), + new Tuple("a",0xe40c292cU), + new Tuple("b",0xe70c2de5U), + new Tuple("c",0xe60c2c52U), + new Tuple("d",0xe10c2473U), + new Tuple("e",0xe00c22e0U), + new Tuple("f",0xe30c2799U), + new Tuple("fo",0x6222e842U), + new Tuple("foo",0xa9f37ed7U), + new Tuple("foob",0x3f5076efU), + }; + + foreach (var testVector in testVectors) + { + byte[] data = fnv.ComputeHash(Encoding.ASCII.GetBytes(testVector.Item1)); + uint value = BitConverter.ToUInt32(data, 0); + Assert.Equal(value, testVector.Item2); + } + } + + [Fact] + public void TestLocator() + { + String[] servers = new[] + { + "10.0.1.1:11211", + "10.0.1.2:11211", + "10.0.1.3:11211", + "10.0.1.4:11211", + "10.0.1.5:11211", + "10.0.1.6:11211", + "10.0.1.7:11211", + "10.0.1.8:11211", + }; + int[] serverCount = new int[servers.Length]; + + var nodes = servers. + Select(s => new MockNode(new IPEndPoint(IPAddress.Parse(s.Substring(0, s.IndexOf(":"))), 11211))). + Cast(). + ToList(); + + IMemcachedNodeLocator locator = new DefaultNodeLocator(); + locator.Initialize(nodes.ToList()); + + var keyCheckCount = 1000000; + var expectedKeysPerServer = keyCheckCount / nodes.Count; + + var random = new Random(); + for (int i = 0; i < keyCheckCount; i++) + { + var node = locator.Locate(random.NextDouble().ToString()); + for (int j = 0; j < nodes.Count; j++) + { + if (nodes[j] == node) + { + serverCount[j]++; + break; + } + } + } + + double maxVariation = 0; + for (int i = 0; i < serverCount.Length; i++) + { + var keysThisServer = serverCount[i]; + var variation = (double)Math.Abs(keysThisServer - expectedKeysPerServer) / expectedKeysPerServer; + maxVariation = Math.Max(maxVariation, variation); + Console.WriteLine("Expected about {0} keys per server; got {1} for server {2}; variation: {3:0.0%}", expectedKeysPerServer, keysThisServer, i, variation); + } + Assert.InRange(maxVariation, 0, 0.20); // variation expected to be less than 20% + } + } + + class MockNode : IMemcachedNode + { + public MockNode(IPEndPoint endpoint) + { + this.EndPoint = endpoint; + } + + public EndPoint EndPoint { get; private set; } + + public bool IsAlive => true; + + public event Action Failed; + + public void Dispose() + { + throw new NotImplementedException(); + } + + public IOperationResult Execute(IOperation op) + { + throw new NotImplementedException(); + } + + public Task ExecuteAsync(IOperation op) + { + throw new NotImplementedException(); + } + + public Task ExecuteAsync(IOperation op, Action next) + { + throw new NotImplementedException(); + } + + public bool Ping() + { + throw new NotImplementedException(); + } + } +}