diff --git a/src/CacheManager.Core/Internal/BackplaneMessage.cs b/src/CacheManager.Core/Internal/BackplaneMessage.cs index 23fa9646..44f5d6ec 100644 --- a/src/CacheManager.Core/Internal/BackplaneMessage.cs +++ b/src/CacheManager.Core/Internal/BackplaneMessage.cs @@ -1,5 +1,6 @@ using System; -using System.Globalization; +using System.Collections.Generic; +using System.Linq; using System.Text; using static CacheManager.Core.Internal.BackplaneAction; using static CacheManager.Core.Utility.Guard; @@ -9,7 +10,7 @@ namespace CacheManager.Core.Internal /// /// Defines the possible actions of the backplane message. /// - public enum BackplaneAction + public enum BackplaneAction : byte { /// /// Default value is invalid to ensure we are not getting wrong results. @@ -41,7 +42,7 @@ public enum BackplaneAction /// /// The enum defines the actual operation used to change the value in the cache. /// - public enum CacheItemChangedEventAction + public enum CacheItemChangedEventAction : byte { /// /// Default value is invalid to ensure we are not getting wrong results. @@ -69,15 +70,15 @@ public enum CacheItemChangedEventAction /// public sealed class BackplaneMessage { - private BackplaneMessage(string owner, BackplaneAction action) + private BackplaneMessage(byte[] owner, BackplaneAction action) { - NotNullOrWhiteSpace(owner, nameof(owner)); + NotNull(owner, nameof(owner)); OwnerIdentity = owner; Action = action; } - private BackplaneMessage(string owner, BackplaneAction action, string key) + private BackplaneMessage(byte[] owner, BackplaneAction action, string key) : this(owner, action) { NotNullOrWhiteSpace(key, nameof(key)); @@ -85,7 +86,7 @@ private BackplaneMessage(string owner, BackplaneAction action, string key) Key = key; } - private BackplaneMessage(string owner, BackplaneAction action, string key, string region) + private BackplaneMessage(byte[] owner, BackplaneAction action, string key, string region) : this(owner, action, key) { NotNullOrWhiteSpace(region, nameof(region)); @@ -93,13 +94,13 @@ private BackplaneMessage(string owner, BackplaneAction action, string key, strin Region = region; } - private BackplaneMessage(string owner, BackplaneAction action, string key, CacheItemChangedEventAction changeAction) + private BackplaneMessage(byte[] owner, BackplaneAction action, string key, CacheItemChangedEventAction changeAction) : this(owner, action, key) { ChangeAction = changeAction; } - private BackplaneMessage(string owner, BackplaneAction action, string key, string region, CacheItemChangedEventAction changeAction) + private BackplaneMessage(byte[] owner, BackplaneAction action, string key, string region, CacheItemChangedEventAction changeAction) : this(owner, action, key, region) { ChangeAction = changeAction; @@ -109,110 +110,90 @@ private BackplaneMessage(string owner, BackplaneAction action, string key, strin /// Gets or sets the action. /// /// The action. - public BackplaneAction Action { get; set; } + public BackplaneAction Action { get; } /// /// Gets or sets the key. /// /// The key. - public string Key { get; set; } + public string Key { get; } /// /// Gets or sets the owner identity. /// /// The owner identity. - public string OwnerIdentity { get; set; } + public byte[] OwnerIdentity { get; } /// /// Gets or sets the region. /// /// The region. - public string Region { get; set; } + public string Region { get; private set; } /// /// Gets or sets the cache action. /// - public CacheItemChangedEventAction ChangeAction { get; set; } + public CacheItemChangedEventAction ChangeAction { get; } - /// - /// Deserializes the . - /// - /// The message. - /// - /// The new instance. - /// - /// If is null. - /// If the message is not valid. - public static BackplaneMessage Deserialize(string message) + /// + public override string ToString() { - NotNullOrWhiteSpace(message, nameof(message)); - - var tokens = message.Split(new[] { ":" }, StringSplitOptions.RemoveEmptyEntries); - - if (tokens.Length < 2) + switch (Action) { - throw new InvalidOperationException("Received an invalid message."); - } + case Changed: + return $"{Action} {Region}:{Key} {ChangeAction}"; - var ident = tokens[0]; - var action = (BackplaneAction)int.Parse(tokens[1], CultureInfo.InvariantCulture); + case Removed: + return $"{Action} {Region}:{Key}"; - if (action == Clear) - { - return new BackplaneMessage(ident, Clear); - } - else if (action == ClearRegion) - { - return new BackplaneMessage(ident, ClearRegion) { Region = Decode(tokens[2]) }; - } - else if (action == Removed) - { - if (tokens.Length < 3) - { - throw new InvalidOperationException("Remove message does not contain valid data."); - } - - if (tokens.Length == 3) - { - return new BackplaneMessage(ident, Removed, Decode(tokens[2])); - } + case ClearRegion: + return $"{Action} {Region}"; - return new BackplaneMessage(ident, Removed, Decode(tokens[2]), Decode(tokens[3])); + case Clear: + return $"{Action}"; } - if (tokens.Length < 4) - { - throw new InvalidOperationException("Change message does not contain valid data."); - } + return string.Empty; + } - var cacheActionVal = tokens[2]; - var changeAction = CacheItemChangedEventAction.Invalid; - if (cacheActionVal.Equals("Put", StringComparison.OrdinalIgnoreCase)) + /// + public override bool Equals(object obj) + { + if (obj == null) { - changeAction = CacheItemChangedEventAction.Put; + return false; } - if (cacheActionVal.Equals("Add", StringComparison.OrdinalIgnoreCase)) + if (object.ReferenceEquals(obj, this)) { - changeAction = CacheItemChangedEventAction.Add; + return true; } - if (cacheActionVal.Equals("Update", StringComparison.OrdinalIgnoreCase)) + var objCast = obj as BackplaneMessage; + if (objCast == null) { - changeAction = CacheItemChangedEventAction.Update; + return false; } - if (changeAction == CacheItemChangedEventAction.Invalid) - { - throw new InvalidOperationException("Received message with invalid change action."); - } + return Action == objCast.Action + && Key == objCast.Key + && ChangeAction == objCast.ChangeAction + && Region == objCast.Region; + } - if (tokens.Length == 4) + /// + public override int GetHashCode() + { + unchecked { - return new BackplaneMessage(ident, action, Decode(tokens[3]), changeAction); - } + var hash = 17; - return new BackplaneMessage(ident, action, Decode(tokens[3]), Decode(tokens[4]), changeAction); + hash = hash * 23 + Action.GetHashCode(); + hash = hash * 23 + ChangeAction.GetHashCode(); + hash = hash * 23 + (Region?.GetHashCode() ?? 17); + hash = hash * 23 + (Key?.GetHashCode() ?? 17); + return hash; + } } /// @@ -222,7 +203,7 @@ public static BackplaneMessage Deserialize(string message) /// The key. /// The cache change action. /// The new instance. - public static BackplaneMessage ForChanged(string owner, string key, CacheItemChangedEventAction changeAction) => + public static BackplaneMessage ForChanged(byte[] owner, string key, CacheItemChangedEventAction changeAction) => new BackplaneMessage(owner, Changed, key, changeAction); /// @@ -233,7 +214,7 @@ public static BackplaneMessage ForChanged(string owner, string key, CacheItemCha /// The region. /// The cache change action. /// The new instance. - public static BackplaneMessage ForChanged(string owner, string key, string region, CacheItemChangedEventAction changeAction) => + public static BackplaneMessage ForChanged(byte[] owner, string key, string region, CacheItemChangedEventAction changeAction) => new BackplaneMessage(owner, Changed, key, region, changeAction); /// @@ -241,7 +222,7 @@ public static BackplaneMessage ForChanged(string owner, string key, string regio /// /// The owner. /// The new instance. - public static BackplaneMessage ForClear(string owner) => + public static BackplaneMessage ForClear(byte[] owner) => new BackplaneMessage(owner, Clear); /// @@ -251,7 +232,7 @@ public static BackplaneMessage ForClear(string owner) => /// The region. /// The new instance. /// If region is null. - public static BackplaneMessage ForClearRegion(string owner, string region) + public static BackplaneMessage ForClearRegion(byte[] owner, string region) { NotNullOrWhiteSpace(region, nameof(region)); @@ -267,7 +248,7 @@ public static BackplaneMessage ForClearRegion(string owner, string region) /// The owner. /// The key. /// The new instance. - public static BackplaneMessage ForRemoved(string owner, string key) => + public static BackplaneMessage ForRemoved(byte[] owner, string key) => new BackplaneMessage(owner, Removed, key); /// @@ -277,48 +258,283 @@ public static BackplaneMessage ForRemoved(string owner, string key) => /// The key. /// The region. /// The new instance. - public static BackplaneMessage ForRemoved(string owner, string key, string region) => + public static BackplaneMessage ForRemoved(byte[] owner, string key, string region) => new BackplaneMessage(owner, Removed, key, region); /// /// Serializes this instance. /// /// The string representing this message. - public string Serialize() + public static byte[] Serialize(params BackplaneMessage[] messages) + { + NotNullOrEmpty(messages, nameof(messages)); + + // calc size + var size = 0; + for (var i = 0; i < messages.Length; i++) + { + size += MessageWriter.GetEstimatedSize(messages[i], i != 0); + } + + var writer = new MessageWriter(size); + + for (var i = 0; i < messages.Length; i++) + { + SerializeMessage(writer, messages[i], i != 0); + } + + return writer.GetBytes(); + } + + private static void SerializeMessage(MessageWriter writer, BackplaneMessage message, bool skipOwner) { - var action = (int)Action; - if (Action == Clear) + if (!skipOwner) { - return OwnerIdentity + ":" + action; + writer.WriteInt(message.OwnerIdentity.Length); + writer.WriteBytes(message.OwnerIdentity); } - else if (Action == ClearRegion) + + writer.WriteByte((byte)message.Action); + switch (message.Action) { - return OwnerIdentity + ":" + action + ":" + Encode(Region); + case Changed: + writer.WriteByte((byte)message.ChangeAction); + if (!string.IsNullOrEmpty(message.Region)) + { + writer.WriteByte(2); + writer.WriteString(message.Region); + } + else + { + writer.WriteByte(1); + } + writer.WriteString(message.Key); + + break; + + case Removed: + if (!string.IsNullOrEmpty(message.Region)) + { + writer.WriteByte(2); + writer.WriteString(message.Region); + } + else + { + writer.WriteByte(1); + } + writer.WriteString(message.Key); + + break; + + case ClearRegion: + writer.WriteString(message.Region); + break; + + case Clear: + break; } - else if (Action == Removed) + } + + /// + /// Deserializes the . + /// + /// The message. + /// If specified, if the first received message has the same owner, all messages will be skipped. + /// + /// The new instance. + /// + /// If is null. + /// If the message is not valid. + public static IEnumerable Deserialize(byte[] message, byte[] skipOwner = null) + { + NotNull(message, nameof(message)); + if (message.Length < 5) + { + throw new ArgumentException("Invalid message"); + } + var reader = new MessageReader(message); + + var first = DeserializeMessage(reader, null); + + if (skipOwner != null) { - if (string.IsNullOrWhiteSpace(Region)) + if (first.OwnerIdentity.SequenceEqual(skipOwner)) { - return OwnerIdentity + ":" + action + ":" + ":" + Encode(Key); + yield break; } - - return OwnerIdentity + ":" + action + ":" + Encode(Key) + ":" + Encode(Region); } - else if (string.IsNullOrWhiteSpace(Region)) + + yield return first; + + while (reader.HasMore()) { - return OwnerIdentity + ":" + action + ":" + ChangeAction + ":" + Encode(Key); + yield return DeserializeMessage(reader, first.OwnerIdentity); } + } + + private static BackplaneMessage DeserializeMessage(MessageReader reader, byte[] existingOwner) + { + var owner = existingOwner ?? reader.ReadBytes(reader.ReadInt()); + var action = (BackplaneAction)reader.ReadByte(); + + switch (action) + { + case Changed: + var changeAction = (CacheItemChangedEventAction)reader.ReadByte(); + if (reader.ReadByte() == 2) + { + var r = reader.ReadString(); + return ForChanged(owner, reader.ReadString(), r, changeAction); + } - return OwnerIdentity + ":" + action + ":" + ChangeAction + ":" + Encode(Key) + ":" + Encode(Region); + return ForChanged(owner, reader.ReadString(), changeAction); + + case Removed: + if (reader.ReadByte() == 2) + { + var r = reader.ReadString(); + return ForRemoved(owner, reader.ReadString(), r); + } + + return ForRemoved(owner, reader.ReadString()); + + case ClearRegion: + return ForClearRegion(owner, reader.ReadString()); + + case Clear: + return ForClear(owner); + + default: + throw new ArgumentException("Invalid message type"); + } } - private static string Decode(string value) + private class MessageWriter { - var bytes = Convert.FromBase64String(value); - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + private static Encoding _encoding = Encoding.UTF8; + private readonly byte[] _buffer; + private int _position = 0; + + public MessageWriter(int size) + { + _buffer = new byte[size]; + } + + public byte[] GetBytes() + { + var result = new byte[_position]; + Buffer.BlockCopy(_buffer, 0, result, 0, _position); + return result; + } + + public void WriteInt(int number) + { + var bytes = BitConverter.GetBytes(number); + WriteBytes(bytes); + } + + public void WriteString(string value) + { + var len = _encoding.GetByteCount(value); + WriteInt(len); + + _encoding.GetBytes(value, 0, value.Length, _buffer, _position); + _position += len; + } + + public void WriteBytes(byte[] bytes) + { + Buffer.BlockCopy(bytes, 0, _buffer, _position, bytes.Length); + _position += bytes.Length; + } + + public void WriteByte(byte b) + { + _buffer[_position] = b; + _position++; + } + + public static int GetEstimatedSize(BackplaneMessage msg, bool skipOwner) + { + // this is only a rough size multiplied by two for getting a roughly sized buffer + int size = 2; // two enums + if (!skipOwner) + { + size += msg.OwnerIdentity.Length * 4; + } + + size += msg.Key?.Length * 4 ?? 0; + size += msg.Region?.Length * 4 ?? 0; + return size * 2; + } } - private static string Encode(string value) => - Convert.ToBase64String(Encoding.UTF8.GetBytes(value)); + private class MessageReader + { + private static Encoding _encoding = Encoding.UTF8; + private readonly byte[] _data; + private int _position = 0; + + public MessageReader(byte[] bytes) + { + _data = bytes; + } + + public bool HasMore() + { + return _data.Length > _position; + } + + public int ReadInt() + { + var pos = (_position += 4); + if (pos > _data.Length) + { + throw new IndexOutOfRangeException("Cannot read INT32, no additional bytes available."); + } + + return BitConverter.ToInt32(_data, pos - 4); + } + + public byte ReadByte() + { + if (_position >= _data.Length) + { + throw new IndexOutOfRangeException("Cannot read byte, no additional bytes available."); + } + + return _data[_position++]; + } + + public byte[] ReadBytes(int length) + { + var result = new byte[length]; + var pos = (_position += length); + if (pos > _data.Length) + { + throw new IndexOutOfRangeException("Cannot read bytes, no additional bytes available."); + } + + Buffer.BlockCopy(_data, pos - length, result, 0, length); + return result; + } + + public string ReadString() + { + var len = ReadInt(); + if (len <= 0) + { + throw new IndexOutOfRangeException("Invalid length for string"); + } + + var pos = (_position += len); + if (pos > _data.Length) + { + throw new IndexOutOfRangeException("Cannot read string, no additional bytes available."); + } + + return _encoding.GetString(_data, pos - len, len); + } + } } } \ No newline at end of file diff --git a/src/CacheManager.Core/Utility/ObjectPool.cs b/src/CacheManager.Core/Utility/ObjectPool.cs index 7d04fb32..f5c005be 100644 --- a/src/CacheManager.Core/Utility/ObjectPool.cs +++ b/src/CacheManager.Core/Utility/ObjectPool.cs @@ -45,7 +45,7 @@ public ObjectPool(IObjectPoolPolicy policy, int? maxItems = null) throw new ArgumentNullException(nameof(policy)); } - if (maxItems == null || maxItems == 0) + if (maxItems == null || maxItems <= 0) { maxItems = Environment.ProcessorCount * 2; } diff --git a/src/CacheManager.StackExchange.Redis/RedisCacheBackplane.cs b/src/CacheManager.StackExchange.Redis/RedisCacheBackplane.cs index b03fd5cd..d62cd409 100644 --- a/src/CacheManager.StackExchange.Redis/RedisCacheBackplane.cs +++ b/src/CacheManager.StackExchange.Redis/RedisCacheBackplane.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using CacheManager.Core; @@ -27,13 +28,14 @@ namespace CacheManager.Redis public sealed class RedisCacheBackplane : CacheBackplane { private readonly string _channelName; - private readonly string _identifier; + private readonly byte[] _identifier; private readonly ILogger _logger; private readonly RedisConnectionManager _connection; - private HashSet _messages = new HashSet(); + private HashSet _messages = new HashSet(); private object _messageLock = new object(); private int _skippedMessages = 0; private bool _sending = false; + private CancellationTokenSource _source = new CancellationTokenSource(); /// /// Initializes a new instance of the class. @@ -48,7 +50,7 @@ public RedisCacheBackplane(ICacheManagerConfiguration configuration, ILoggerFact _logger = loggerFactory.CreateLogger(this); _channelName = configuration.BackplaneChannelName ?? "CacheManagerBackplane"; - _identifier = Guid.NewGuid().ToString(); + _identifier = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()); var cfg = RedisConfigurations.GetConfiguration(ConfigurationKey); _connection = new RedisConnectionManager( @@ -128,6 +130,7 @@ protected override void Dispose(bool managed) { try { + _source.Cancel(); _connection.Subscriber.Unsubscribe(_channelName); } catch @@ -138,15 +141,13 @@ protected override void Dispose(bool managed) base.Dispose(managed); } - private void Publish(string message) + private void Publish(byte[] message) { _connection.Subscriber.Publish(_channelName, message, CommandFlags.HighPriority); } private void PublishMessage(BackplaneMessage message) { - var msg = message.Serialize(); - lock (_messageLock) { if (message.Action == BackplaneAction.Clear) @@ -155,12 +156,12 @@ private void PublishMessage(BackplaneMessage message) _messages.Clear(); } - if (!_messages.Add(msg)) + if (!_messages.Add(message)) { Interlocked.Increment(ref _skippedMessages); if (_logger.IsEnabled(LogLevel.Trace)) { - _logger.LogTrace("Skipped duplicate message: {0}.", msg); + _logger.LogTrace("Skipped duplicate message: {0}.", message); } } @@ -188,12 +189,12 @@ private void SendMessages() #if !NET40 await Task.Delay(10).ConfigureAwait(false); #endif - var msgs = string.Empty; + byte[] msgs = null; lock (_messageLock) { if (_messages != null && _messages.Count > 0) { - msgs = string.Join(",", _messages); + msgs = BackplaneMessage.Serialize(_messages.ToArray()); if (_logger.IsEnabled(LogLevel.Debug)) { @@ -207,7 +208,7 @@ private void SendMessages() try { - if (msgs.Length > 0) + if (msgs != null) { Publish(msgs); Interlocked.Increment(ref SentChunks); @@ -223,14 +224,14 @@ private void SendMessages() #if NET40 }, this, - CancellationToken.None, + _source.Token, TaskCreationOptions.None, TaskScheduler.Default) .ConfigureAwait(false); #else }, this, - CancellationToken.None, + _source.Token, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default) .ConfigureAwait(false); @@ -243,27 +244,25 @@ private void Subscribe() _channelName, (channel, msg) => { - var fullMessage = ((string)msg).Split(',') - .Where(s => !s.StartsWith(_identifier, StringComparison.Ordinal)) - .ToArray(); + var messages = BackplaneMessage.Deserialize(msg, _identifier); - if (fullMessage.Length == 0) + if (!messages.Any()) { // no messages for this instance return; } - Interlocked.Add(ref MessagesReceived, fullMessage.Length); + // now deserialize all of them (lazy enumerable) + var fullMessages = messages.ToArray(); + Interlocked.Add(ref MessagesReceived, fullMessages.Length); if (_logger.IsEnabled(LogLevel.Information)) { - _logger.LogInfo("Backplane got notified with {0} new messages.", fullMessage.Length); + _logger.LogInfo("Backplane got notified with {0} new messages.", fullMessages.Length); } - foreach (var messageStr in fullMessage) + foreach (var message in fullMessages) { - var message = BackplaneMessage.Deserialize(messageStr); - switch (message.Action) { case BackplaneAction.Clear: diff --git a/test/CacheManager.Benchmarks/BackplaneMessageBenchmark.cs b/test/CacheManager.Benchmarks/BackplaneMessageBenchmark.cs new file mode 100644 index 00000000..9ddefcc5 --- /dev/null +++ b/test/CacheManager.Benchmarks/BackplaneMessageBenchmark.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BenchmarkDotNet.Attributes; +using CacheManager.Core.Internal; + +namespace CacheManager.Benchmarks +{ + public class BackplaneMessageBenchmarkMultiple + { + private static byte[] _ownderBytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()); + private static BackplaneMessage[] _multiple; + private byte[] _multipleSerialized = BackplaneMessage.Serialize(_multiple); + + static BackplaneMessageBenchmarkMultiple() + { + var messages = new List(); + for (var i = 0; i < 10; i++) + { + messages.Add(BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey" + i, CacheItemChangedEventAction.Update)); + messages.Add(BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey" + i, "withregion", CacheItemChangedEventAction.Add)); + } + for (var i = 0; i < 10; i++) + { + messages.Add(BackplaneMessage.ForClear(_ownderBytes)); + } + for (var i = 0; i < 10; i++) + { + messages.Add(BackplaneMessage.ForClearRegion(_ownderBytes, "somerandomregion" + i)); + } + for (var i = 0; i < 10; i++) + { + messages.Add(BackplaneMessage.ForRemoved(_ownderBytes, "somerandomkey" + i, "withregion")); + } + _multiple = messages.ToArray(); + } + + [Benchmark] + public void SerializeMany() + { + var bytes = BackplaneMessage.Serialize(_multiple); + } + + [Benchmark()] + public void DeserializeMany() + { + var messages = BackplaneMessage.Deserialize(_multipleSerialized).ToArray(); + } + } + + public class BackplaneMessageBenchmark + { + private static byte[] _ownderBytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()); + + private static BackplaneMessage _dataSingleChange = BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey", CacheItemChangedEventAction.Update); + private byte[] _rawSingleChange = BackplaneMessage.Serialize(_dataSingleChange); + + private static BackplaneMessage _dataSingleChangeRegion = BackplaneMessage.ForChanged(_ownderBytes, "somerandomkey", "withregion", CacheItemChangedEventAction.Add); + private byte[] _rawSingleChangeRegion = BackplaneMessage.Serialize(_dataSingleChangeRegion); + + private static BackplaneMessage _dataSingleClear = BackplaneMessage.ForClear(_ownderBytes); + private byte[] _rawSingleClear = BackplaneMessage.Serialize(_dataSingleClear); + + private static BackplaneMessage _dataSingleClearRegion = BackplaneMessage.ForClearRegion(_ownderBytes, "somerandomregion"); + private byte[] _rawSingleClearRegion = BackplaneMessage.Serialize(_dataSingleClearRegion); + + private static BackplaneMessage _dataSingleRemove = BackplaneMessage.ForRemoved(_ownderBytes, "somerandomkey", "withregion"); + private byte[] _rawSingleRemove = BackplaneMessage.Serialize(_dataSingleRemove); + + [Benchmark(Baseline = true)] + public void SerializeChange() + { + var fullMessage = BackplaneMessage.Serialize(_dataSingleChange); + } + + [Benchmark()] + public void DeserializeChange() + { + var msg = BackplaneMessage.Deserialize(_rawSingleChange).ToArray(); + } + + [Benchmark] + public void SerializeChangeRegion() + { + var fullMessage = BackplaneMessage.Serialize(_dataSingleChangeRegion); + } + + [Benchmark()] + public void DeserializeChangeRegion() + { + var msg = BackplaneMessage.Deserialize(_rawSingleChangeRegion).ToArray(); + } + + [Benchmark] + public void SerializeClear() + { + var fullMessage = BackplaneMessage.Serialize(_dataSingleClear); + } + + [Benchmark()] + public void DeserializeClear() + { + var msg = BackplaneMessage.Deserialize(_rawSingleClear).ToArray(); + } + + [Benchmark] + public void SerializeClearRegion() + { + var fullMessage = BackplaneMessage.Serialize(_dataSingleClearRegion); + } + + [Benchmark()] + public void DeserializeClearRegion() + { + var msg = BackplaneMessage.Deserialize(_rawSingleClearRegion).ToArray(); + } + + [Benchmark] + public void SerializeRemove() + { + var fullMessage = BackplaneMessage.Serialize(_dataSingleRemove); + } + + [Benchmark()] + public void DeserializeRemove() + { + var msg = BackplaneMessage.Deserialize(_rawSingleRemove).ToArray(); + } + } +} \ No newline at end of file diff --git a/test/CacheManager.Benchmarks/BaseCacheManagerBenchmark.cs b/test/CacheManager.Benchmarks/BaseCacheManagerBenchmark.cs index 70dc472c..b165682c 100644 --- a/test/CacheManager.Benchmarks/BaseCacheManagerBenchmark.cs +++ b/test/CacheManager.Benchmarks/BaseCacheManagerBenchmark.cs @@ -7,7 +7,6 @@ namespace CacheManager.Benchmarks { - [Config(typeof(CacheManagerBenchConfig))] public abstract class BaseCacheBenchmark { private static ICacheManagerConfiguration BaseConfig diff --git a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report-github.md b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report-github.md new file mode 100644 index 00000000..d7468b8d --- /dev/null +++ b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report-github.md @@ -0,0 +1,24 @@ +``` ini + +BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063) +Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8 +Frequency=3328125 Hz, Resolution=300.4695 ns, Timer=TSC + [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0 + Job-VUBPRE : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0 + +Runtime=Clr LaunchCount=1 TargetCount=10 +WarmupCount=4 + +``` + | Method | Mean | Error | StdDev | Median | Op/s | Scaled | ScaledSD | Rank | Gen 0 | Allocated | + |------------------------ |----------:|----------:|---------:|----------:|-------------:|-------:|---------:|-----:|-------:|----------:| + | SerializeChange | 194.57 ns | 11.309 ns | 7.480 ns | 192.86 ns | 5,139,629.5 | 1.00 | 0.00 | 4 | 0.1523 | 640 B | + | DeserializeChange | 241.69 ns | 10.354 ns | 6.848 ns | 238.51 ns | 4,137,485.5 | 1.24 | 0.06 | 5 | 0.0875 | 368 B | + | SerializeChangeRegion | 249.23 ns | 7.700 ns | 4.582 ns | 249.67 ns | 4,012,334.1 | 1.28 | 0.05 | 6 | 0.1826 | 768 B | + | DeserializeChangeRegion | 305.05 ns | 8.953 ns | 5.922 ns | 306.25 ns | 3,278,156.0 | 1.57 | 0.06 | 7 | 0.0987 | 416 B | + | SerializeClear | 96.21 ns | 4.459 ns | 2.949 ns | 95.74 ns | 10,393,877.9 | 0.50 | 0.02 | 1 | 0.1162 | 488 B | + | DeserializeClear | 158.92 ns | 7.949 ns | 5.258 ns | 159.25 ns | 6,292,374.9 | 0.82 | 0.04 | 2 | 0.0741 | 312 B | + | SerializeClearRegion | 169.66 ns | 7.262 ns | 3.798 ns | 169.60 ns | 5,894,312.1 | 0.87 | 0.04 | 3 | 0.1581 | 664 B | + | DeserializeClearRegion | 240.22 ns | 8.661 ns | 5.154 ns | 240.20 ns | 4,162,849.7 | 1.24 | 0.05 | 5 | 0.0892 | 376 B | + | SerializeRemove | 255.63 ns | 15.071 ns | 9.968 ns | 258.50 ns | 3,911,924.7 | 1.32 | 0.07 | 6 | 0.1826 | 768 B | + | DeserializeRemove | 300.98 ns | 9.420 ns | 5.606 ns | 299.66 ns | 3,322,483.1 | 1.55 | 0.06 | 7 | 0.0987 | 416 B | diff --git a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report.csv b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report.csv new file mode 100644 index 00000000..67ac40c6 --- /dev/null +++ b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report.csv @@ -0,0 +1,11 @@ +Method;Job;AnalyzeLaunchVariance;EvaluateOverhead;MaxAbsoluteError;MaxRelativeError;MinInvokeCount;MinIterationTime;RemoveOutliers;Affinity;Jit;Platform;Runtime;AllowVeryLargeObjects;Concurrent;CpuGroups;Force;RetainVm;Server;Clock;EngineFactory;Toolchain;InvocationCount;IterationTime;LaunchCount;RunStrategy;TargetCount;UnrollFactor;WarmupCount;Mean;Error;StdDev;Median;Op/s;Scaled;ScaledSD;Rank;Gen 0;Allocated +SerializeChange;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;194.57 ns;11.309 ns;7.480 ns;192.86 ns;"5,139,629.5";1.00;0.00;4;0.1523;640 B +DeserializeChange;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;241.69 ns;10.354 ns;6.848 ns;238.51 ns;"4,137,485.5";1.24;0.06;5;0.0875;368 B +SerializeChangeRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;249.23 ns;7.700 ns;4.582 ns;249.67 ns;"4,012,334.1";1.28;0.05;6;0.1826;768 B +DeserializeChangeRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;305.05 ns;8.953 ns;5.922 ns;306.25 ns;"3,278,156.0";1.57;0.06;7;0.0987;416 B +SerializeClear;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;96.21 ns;4.459 ns;2.949 ns;95.74 ns;"10,393,877.9";0.50;0.02;1;0.1162;488 B +DeserializeClear;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;158.92 ns;7.949 ns;5.258 ns;159.25 ns;"6,292,374.9";0.82;0.04;2;0.0741;312 B +SerializeClearRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;169.66 ns;7.262 ns;3.798 ns;169.60 ns;"5,894,312.1";0.87;0.04;3;0.1581;664 B +DeserializeClearRegion;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;240.22 ns;8.661 ns;5.154 ns;240.20 ns;"4,162,849.7";1.24;0.05;5;0.0892;376 B +SerializeRemove;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;255.63 ns;15.071 ns;9.968 ns;258.50 ns;"3,911,924.7";1.32;0.07;6;0.1826;768 B +DeserializeRemove;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;300.98 ns;9.420 ns;5.606 ns;299.66 ns;"3,322,483.1";1.55;0.06;7;0.0987;416 B diff --git a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report.html b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report.html new file mode 100644 index 00000000..b39c51c1 --- /dev/null +++ b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmark-report.html @@ -0,0 +1,41 @@ + + + + +BackplaneMessageBenchmark + + + + +

+BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
+Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8
+Frequency=3328125 Hz, Resolution=300.4695 ns, Timer=TSC
+  [Host]     : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
+  Job-VUBPRE : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
+
+
Runtime=Clr  LaunchCount=1  TargetCount=10  
+WarmupCount=4  
+
+ + + + + + + + + + + + + + +
MethodMeanErrorStdDevMedian Op/sScaledScaledSDRankGen 0Allocated
SerializeChange194.57 ns11.309 ns7.480 ns192.86 ns5,139,629.51.000.0040.1523640 B
DeserializeChange241.69 ns10.354 ns6.848 ns238.51 ns4,137,485.51.240.0650.0875368 B
SerializeChangeRegion249.23 ns7.700 ns4.582 ns249.67 ns4,012,334.11.280.0560.1826768 B
DeserializeChangeRegion305.05 ns8.953 ns5.922 ns306.25 ns3,278,156.01.570.0670.0987416 B
SerializeClear96.21 ns4.459 ns2.949 ns95.74 ns10,393,877.90.500.0210.1162488 B
DeserializeClear158.92 ns7.949 ns5.258 ns159.25 ns6,292,374.90.820.0420.0741312 B
SerializeClearRegion169.66 ns7.262 ns3.798 ns169.60 ns5,894,312.10.870.0430.1581664 B
DeserializeClearRegion240.22 ns8.661 ns5.154 ns240.20 ns4,162,849.71.240.0550.0892376 B
SerializeRemove255.63 ns15.071 ns9.968 ns258.50 ns3,911,924.71.320.0760.1826768 B
DeserializeRemove300.98 ns9.420 ns5.606 ns299.66 ns3,322,483.11.550.0670.0987416 B
+ + diff --git a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report-github.md b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report-github.md new file mode 100644 index 00000000..74c67239 --- /dev/null +++ b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report-github.md @@ -0,0 +1,16 @@ +``` ini + +BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063) +Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8 +Frequency=3328125 Hz, Resolution=300.4695 ns, Timer=TSC + [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0 + Job-VUBPRE : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0 + +Runtime=Clr LaunchCount=1 TargetCount=10 +WarmupCount=4 + +``` + | Method | Mean | Error | StdDev | Median | Op/s | Rank | Gen 0 | Allocated | + |---------------- |---------:|----------:|----------:|---------:|----------:|-----:|-------:|----------:| + | SerializeMany | 5.650 us | 0.1873 us | 0.1239 us | 5.626 us | 177,004.2 | 1 | 2.3804 | 9.78 KB | + | DeserializeMany | 7.132 us | 0.3477 us | 0.2300 us | 7.115 us | 140,203.9 | 2 | 1.7548 | 7.22 KB | diff --git a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report.csv b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report.csv new file mode 100644 index 00000000..4c935232 --- /dev/null +++ b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report.csv @@ -0,0 +1,3 @@ +Method;Job;AnalyzeLaunchVariance;EvaluateOverhead;MaxAbsoluteError;MaxRelativeError;MinInvokeCount;MinIterationTime;RemoveOutliers;Affinity;Jit;Platform;Runtime;AllowVeryLargeObjects;Concurrent;CpuGroups;Force;RetainVm;Server;Clock;EngineFactory;Toolchain;InvocationCount;IterationTime;LaunchCount;RunStrategy;TargetCount;UnrollFactor;WarmupCount;Mean;Error;StdDev;Median;Op/s;Rank;Gen 0;Allocated +SerializeMany;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;5.650 us;0.1873 us;0.1239 us;5.626 us;"177,004.2";1;2.3804;9.78 KB +DeserializeMany;Default;False;Default;Default;Default;Default;Default;Default;255;RyuJit;X64;Clr;False;True;False;True;False;False;Default;Default;Default;1;Default;1;Default;10;16;4;7.132 us;0.3477 us;0.2300 us;7.115 us;"140,203.9";2;1.7548;7.22 KB diff --git a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report.html b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report.html new file mode 100644 index 00000000..566df967 --- /dev/null +++ b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/BackplaneMessageBenchmarkMultiple-report.html @@ -0,0 +1,33 @@ + + + + +BackplaneMessageBenchmarkMultiple + + + + +

+BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
+Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8
+Frequency=3328125 Hz, Resolution=300.4695 ns, Timer=TSC
+  [Host]     : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
+  Job-VUBPRE : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2098.0
+
+
Runtime=Clr  LaunchCount=1  TargetCount=10  
+WarmupCount=4  
+
+ + + + + + +
MethodMeanErrorStdDevMedianOp/sRankGen 0Allocated
SerializeMany5.650 us0.1873 us0.1239 us5.626 us177,004.212.38049.78 KB
DeserializeMany7.132 us0.3477 us0.2300 us7.115 us140,203.921.75487.22 KB
+ + diff --git a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/GzBenchmark-report-github.md b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/GzBenchmark-report-github.md index 996b0a08..b6efab5b 100644 --- a/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/GzBenchmark-report-github.md +++ b/test/CacheManager.Benchmarks/BenchmarkDotNet.Artifacts/results/GzBenchmark-report-github.md @@ -10,7 +10,8 @@ Platform=X64 LaunchCount=2 TargetCount=15 WarmupCount=10 ``` - Method | Mean | StdDev | Scaled | Scaled-StdDev | Gen 0 | Allocated | + +Method | Mean | StdDev | Scaled | Scaled-StdDev | Gen 0 | Allocated | ------------- |---------- |---------- |------- |-------------- |-------- |---------- | Naive | 1.3792 ms | 0.0211 ms | 1.00 | 0.00 | 29.6875 | 301.06 kB | Manuel | 1.3892 ms | 0.0465 ms | 1.01 | 0.04 | 5.4688 | 191.09 kB | diff --git a/test/CacheManager.Benchmarks/CacheManager.Benchmarks.csproj b/test/CacheManager.Benchmarks/CacheManager.Benchmarks.csproj index 2d90d266..f34ceb8f 100644 --- a/test/CacheManager.Benchmarks/CacheManager.Benchmarks.csproj +++ b/test/CacheManager.Benchmarks/CacheManager.Benchmarks.csproj @@ -18,7 +18,7 @@ - + @@ -27,4 +27,4 @@ - + \ No newline at end of file diff --git a/test/CacheManager.Benchmarks/GzBenchmark.cs b/test/CacheManager.Benchmarks/GzBenchmark.cs index cc96929a..ad50d49a 100644 --- a/test/CacheManager.Benchmarks/GzBenchmark.cs +++ b/test/CacheManager.Benchmarks/GzBenchmark.cs @@ -9,7 +9,6 @@ namespace CacheManager.Benchmarks { - [Config(typeof(CacheManagerBenchConfig))] public class GzBenchmark { private static ArrayPool _pool = ArrayPool.Create(); diff --git a/test/CacheManager.Benchmarks/PlainDictionaryUpdateBenchmark.cs b/test/CacheManager.Benchmarks/PlainDictionaryUpdateBenchmark.cs index fb314a61..8b7fd44f 100644 --- a/test/CacheManager.Benchmarks/PlainDictionaryUpdateBenchmark.cs +++ b/test/CacheManager.Benchmarks/PlainDictionaryUpdateBenchmark.cs @@ -6,7 +6,6 @@ namespace CacheManager.Benchmarks { - [Config(typeof(CacheManagerBenchConfig))] public class PlainDictionaryUpdateBenchmark { private const int Threads = 6; @@ -59,7 +58,7 @@ public int UpdateImpl(Func updateFactory) success = _dictionary.TryUpdate("key", updateFactory(value), value); } while (!success); - + return value; } diff --git a/test/CacheManager.Benchmarks/Program.cs b/test/CacheManager.Benchmarks/Program.cs index 97114a88..06074b7f 100644 --- a/test/CacheManager.Benchmarks/Program.cs +++ b/test/CacheManager.Benchmarks/Program.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reflection; +using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; @@ -21,11 +22,40 @@ public class Program { public static void Main(string[] args) { + //new BackplaneMessageBenchmark().DeserializeChange(); + new BackplaneMessageBenchmarkMultiple().SerializeMany(); + new BackplaneMessageBenchmarkMultiple().DeserializeMany(); + do { + var config = ManualConfig.Create(DefaultConfig.Instance) + //.With(exporters: BenchmarkDotNet.Exporters.DefaultExporters.) + .With(BenchmarkDotNet.Analysers.EnvironmentAnalyser.Default) + .With(BenchmarkDotNet.Exporters.MarkdownExporter.GitHub) + .With(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default) + .With(StatisticColumn.Mean) + .With(StatisticColumn.Median) + //.With(StatisticColumn.Min) + //.With(StatisticColumn.Max) + .With(StatisticColumn.StdDev) + .With(StatisticColumn.OperationsPerSecond) + .With(BaselineScaledColumn.Scaled) + //.With(BaselineScaledColumn.ScaledStdDev) + .With(RankColumn.Arabic) + + .With(Job.Clr + .WithTargetCount(10) + .WithWarmupCount(4) + .WithLaunchCount(1)); + + //.With(Job.Clr + // .WithTargetCount(10) + // .WithWarmupCount(5) + // .WithLaunchCount(1)); + BenchmarkSwitcher .FromAssembly(typeof(Program).GetTypeInfo().Assembly) - .Run(args); + .Run(args, config); Console.WriteLine("done!"); Console.WriteLine("Press escape to exit or any key to continue..."); diff --git a/test/CacheManager.Benchmarks/SerializationBenchmark.cs b/test/CacheManager.Benchmarks/SerializationBenchmark.cs index 727aa514..34ffe55a 100644 --- a/test/CacheManager.Benchmarks/SerializationBenchmark.cs +++ b/test/CacheManager.Benchmarks/SerializationBenchmark.cs @@ -18,7 +18,6 @@ namespace CacheManager.Benchmarks { - [Config(typeof(CacheManagerBenchConfig))] public class SerializationBenchmark { private int _iterations = 1000; @@ -66,14 +65,14 @@ public void Setup() _payload = new Queue>(items); } - + private void ExecRun(Action> action) { var item = _payload.Dequeue(); action(item); _payload.Enqueue(item); } - + [Benchmark()] public void BinarySerializer() { diff --git a/test/CacheManager.Benchmarks/UnixTimestampBenchmark.cs b/test/CacheManager.Benchmarks/UnixTimestampBenchmark.cs index c3f55e45..7c52f650 100644 --- a/test/CacheManager.Benchmarks/UnixTimestampBenchmark.cs +++ b/test/CacheManager.Benchmarks/UnixTimestampBenchmark.cs @@ -5,7 +5,6 @@ namespace CacheManager.Benchmarks { - [Config(typeof(CacheManagerBenchConfig))] public class UnixTimestampBenchmark { [Benchmark(Baseline = true)] @@ -21,7 +20,7 @@ public long ManualCalcNaive() { return (long)(DateTime.UtcNow - _date1970).TotalMilliseconds; } - + [Benchmark()] public long ManualCalcOptimized() { diff --git a/test/CacheManager.Events.Tests/EventCommand.cs b/test/CacheManager.Events.Tests/EventCommand.cs index f3b6e4ab..c6f35a9c 100644 --- a/test/CacheManager.Events.Tests/EventCommand.cs +++ b/test/CacheManager.Events.Tests/EventCommand.cs @@ -86,6 +86,7 @@ protected async Task Runner(Func task, params EventCounter p.Cache.Name))}; showing [local][remote] events."); try { var reportTask = Task.Run(async () => @@ -94,7 +95,12 @@ protected async Task Runner(Func task, params EventCounter(Func task, params EventCounter GetStatus(EventCounter[] handlings) + private static IEnumerable GetStatus(EventCounter[] handlings, bool printEmpty = false) { foreach (var handling in handlings) { @@ -146,7 +152,10 @@ private static IEnumerable GetStatus(EventCounter p > 0)) + { + report.Append($"{kv.Key}:[{string.Join("][", kv.Value)}] "); + } } //Console.WriteLine("Expected: " + report.ToString()); diff --git a/test/CacheManager.Events.Tests/EventHandling.cs b/test/CacheManager.Events.Tests/EventHandling.cs index 9d75a0a1..5d61bd8a 100644 --- a/test/CacheManager.Events.Tests/EventHandling.cs +++ b/test/CacheManager.Events.Tests/EventHandling.cs @@ -23,14 +23,13 @@ public EventCounter(ICacheManager cache) Cache.OnPut += OnPut; Cache.OnUpdate += OnUpdate; - _updates.Add(CacheEvent.Add, new int[1]); - _updates.Add(CacheEvent.Put, new int[1]); - _updates.Add(CacheEvent.Rem, new int[1]); - _updates.Add(CacheEvent.Get, new int[1]); - _updates.Add(CacheEvent.ReH, new int[1]); - _updates.Add(CacheEvent.Upd, new int[1]); + _updates.Add(CacheEvent.Add, new int[2]); + _updates.Add(CacheEvent.Put, new int[2]); + _updates.Add(CacheEvent.Rem, new int[2]); + _updates.Add(CacheEvent.Get, new int[2]); + _updates.Add(CacheEvent.ReH, new int[2]); + _updates.Add(CacheEvent.Upd, new int[2]); } - public Dictionary GetExpectedState() { @@ -38,47 +37,54 @@ public Dictionary GetExpectedState() foreach (var kv in _updates.ToArray()) { - result.Add(kv.Key, new[] { kv.Value[0] }); + result.Add(kv.Key, new[] { kv.Value[0], kv.Value[1] }); } return result; } - private void Update(CacheEvent ev, string key) + private void Update(CacheEvent ev, string key, CacheActionEventArgOrigin origin) { - Interlocked.Increment(ref _updates[ev][0]); + if (origin == CacheActionEventArgOrigin.Local) + { + Interlocked.Increment(ref _updates[ev][0]); + } + else + { + Interlocked.Increment(ref _updates[ev][1]); + } } public ICacheManager Cache { get; } private void OnUpdate(object sender, CacheActionEventArgs e) { - Update(CacheEvent.Upd, e.Key); + Update(CacheEvent.Upd, e.Key, e.Origin); } private void OnPut(object sender, CacheActionEventArgs e) { - Update(CacheEvent.Put, e.Key); + Update(CacheEvent.Put, e.Key, e.Origin); } private void OnGet(object sender, CacheActionEventArgs e) { - Update(CacheEvent.Get, e.Key); + Update(CacheEvent.Get, e.Key, e.Origin); } private void OnAdd(object sender, CacheActionEventArgs e) { - Update(CacheEvent.Add, e.Key); + Update(CacheEvent.Add, e.Key, e.Origin); } private void OnRemove(object sender, CacheActionEventArgs e) { - Update(CacheEvent.Rem, e.Key); + Update(CacheEvent.Rem, e.Key, e.Origin); } private void OnRemoveByHandle(object sender, CacheItemRemovedEventArgs e) { - Update(CacheEvent.ReH, e.Key); + Update(CacheEvent.ReH, e.Key, CacheActionEventArgOrigin.Local); } } } \ No newline at end of file diff --git a/test/CacheManager.Events.Tests/Program.cs b/test/CacheManager.Events.Tests/Program.cs index a6bbf823..fea3c31a 100644 --- a/test/CacheManager.Events.Tests/Program.cs +++ b/test/CacheManager.Events.Tests/Program.cs @@ -14,7 +14,9 @@ private static void Main(string[] args) .AddConsole(LogLevel.Warning); var app = new CommandLineApplication(false); + app.Command("redis", (cmdApp) => new RedisCommand(cmdApp, loggerFactory), throwOnUnexpectedArg: true); app.Command("redisAndMemory", (cmdApp) => new RedisAndMemoryCommand(cmdApp, loggerFactory), throwOnUnexpectedArg: true); + app.Command("redisAndMemoryNoMessages", (cmdApp) => new RedisAndMemoryNoMessagingCommand(cmdApp, loggerFactory), throwOnUnexpectedArg: true); app.Command("memoryOnly", (cmdApp) => new MemoryOnlyCommand(cmdApp, loggerFactory), throwOnUnexpectedArg: true); app.HelpOption("-h|--help"); if (args.Length == 0) diff --git a/test/CacheManager.Events.Tests/Properties/launchSettings.json b/test/CacheManager.Events.Tests/Properties/launchSettings.json index 335c2a11..a8986eb0 100644 --- a/test/CacheManager.Events.Tests/Properties/launchSettings.json +++ b/test/CacheManager.Events.Tests/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "CacheManager.Events.Tests": { "commandName": "Project", - "commandLineArgs": "redisAndMemory -r 50 -j 1" + "commandLineArgs": "redis -r 20 -j 10" } } } \ No newline at end of file diff --git a/test/CacheManager.Events.Tests/RedisAndMemoryCommand.cs b/test/CacheManager.Events.Tests/RedisAndMemoryCommand.cs index 5d084df3..5a550c18 100644 --- a/test/CacheManager.Events.Tests/RedisAndMemoryCommand.cs +++ b/test/CacheManager.Events.Tests/RedisAndMemoryCommand.cs @@ -8,10 +8,53 @@ namespace CacheManager.Events.Tests { + public class RedisAndMemoryNoMessagingCommand : RedisAndMemoryCommand + { + public RedisAndMemoryNoMessagingCommand(CommandLineApplication app, ILoggerFactory loggerFactory) : base(app, loggerFactory) + { + } + + protected override void Configure() + { + base.Configure(); + + _multiplexer = ConnectionMultiplexer.Connect("127.0.0.1,allowAdmin=true"); + _configuration = new ConfigurationBuilder() + .WithMicrosoftLogging(LoggerFactory) + .WithMicrosoftMemoryCacheHandle("in-memory") + .And + .WithJsonSerializer() + .WithRedisConfiguration("redisConfig", _multiplexer, enableKeyspaceNotifications: false) + .WithRedisCacheHandle("redisConfig") + .Build(); + } + } + + public class RedisCommand : RedisAndMemoryCommand + { + public RedisCommand(CommandLineApplication app, ILoggerFactory loggerFactory) : base(app, loggerFactory) + { + } + + protected override void Configure() + { + base.Configure(); + + _multiplexer = ConnectionMultiplexer.Connect("127.0.0.1,allowAdmin=true"); + _configuration = new ConfigurationBuilder() + .WithMicrosoftLogging(LoggerFactory) + .WithRedisBackplane("redisConfig") + .WithJsonSerializer() + .WithRedisConfiguration("redisConfig", _multiplexer, enableKeyspaceNotifications: true) + .WithRedisCacheHandle("redisConfig") + .Build(); + } + } + public class RedisAndMemoryCommand : EventCommand { - private ICacheManagerConfiguration _configuration; - private ConnectionMultiplexer _multiplexer; + protected ICacheManagerConfiguration _configuration; + protected ConnectionMultiplexer _multiplexer; public RedisAndMemoryCommand(CommandLineApplication app, ILoggerFactory loggerFactory) : base(app, loggerFactory) { @@ -21,7 +64,7 @@ protected override void Configure() { base.Configure(); - _multiplexer = ConnectionMultiplexer.Connect("localhost,allowAdmin=true"); + _multiplexer = ConnectionMultiplexer.Connect("127.0.0.1,allowAdmin=true"); _configuration = new ConfigurationBuilder() .WithMicrosoftLogging(LoggerFactory) .WithMicrosoftMemoryCacheHandle("in-memory") @@ -46,86 +89,13 @@ public override async Task Execute() var rndNumber = rnd.Next(42, 420); var key = Guid.NewGuid().ToString(); - bool didRemove = false; - bool didUpdate = false; - bool removeTriggeredA = false; - bool updateTriggeredA = false; - bool removeTriggeredB = false; - bool updateTriggeredB = false; - - void OnUpdate(object sender, CacheActionEventArgs args) - { - if (args.Key.Equals(key)) - { - if (!didUpdate) - { - Console.WriteLine("Key has been updated without me calling it"); - } - else if (sender.Equals(cacheA)) - { - updateTriggeredA = true; - } - else if (sender.Equals(cacheB)) - { - updateTriggeredB = true; - } - } - } - - void OnRemove(object sender, CacheActionEventArgs args) - { - if (args.Key.Equals(key)) - { - if (!didRemove) - { - Console.WriteLine("Key has been removed without me removing it"); - } - else if (sender.Equals(cacheA)) - { - removeTriggeredA = true; - } - else if (sender.Equals(cacheB)) - { - removeTriggeredB = true; - } - } - } - - cacheA.OnUpdate += OnUpdate; - cacheA.OnRemove += OnRemove; - cacheB.OnUpdate += OnUpdate; - cacheB.OnRemove += OnRemove; - - while (!cacheA.Add(key, rndNumber)) - { - key = Guid.NewGuid().ToString(); - } - - didUpdate = true; - cacheA.TryUpdate(key, (oldVal) => oldVal + 1, out int? newValue); - //while (!updateTriggeredA || !updateTriggeredB) - //{ - // await Task.Delay(1); - //} + cacheA.Add(key, rndNumber); - if (cacheA[key] != cacheB[key] && cacheA[key] != null) - { - // log warn? - } + cacheA.TryUpdate(key, (oldVal) => oldVal + 1, out int? newValue); - await Task.Delay(0); - didRemove = true; _multiplexer.GetDatabase(0).KeyDelete(key, CommandFlags.HighPriority); - //while (!removeTriggeredA || !removeTriggeredB) - //{ - // await Task.Delay(1); - //} - - cacheA.OnUpdate -= OnUpdate; - cacheA.OnRemove -= OnRemove; - cacheB.OnUpdate -= OnUpdate; - cacheB.OnRemove -= OnRemove; + await Task.Delay(0); }); } catch (Exception ex) diff --git a/test/CacheManager.Tests/BackplaneMessageTest.cs b/test/CacheManager.Tests/BackplaneMessageTest.cs new file mode 100644 index 00000000..753ffe14 --- /dev/null +++ b/test/CacheManager.Tests/BackplaneMessageTest.cs @@ -0,0 +1,647 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using CacheManager.Core.Internal; +using FluentAssertions; +using Xunit; + +namespace CacheManager.Tests +{ + [ExcludeFromCodeCoverage] + public class BackplaneMessageTest + { + [Fact] + public void BackplaneMessage_ChangeAddKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, CacheItemChangedEventAction.Add); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(null); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Add); + deserialized.Action.Should().Be(BackplaneAction.Changed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_ChangeAddKeyRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Add); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(region); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Add); + deserialized.Action.Should().Be(BackplaneAction.Changed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_ChangePutKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, CacheItemChangedEventAction.Put); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(null); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Put); + deserialized.Action.Should().Be(BackplaneAction.Changed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_ChangePutKeyRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Put); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(region); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Put); + deserialized.Action.Should().Be(BackplaneAction.Changed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_ChangeUpdateKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, CacheItemChangedEventAction.Update); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(null); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Update); + deserialized.Action.Should().Be(BackplaneAction.Changed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_ChangePutUpdateRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(region); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Update); + deserialized.Action.Should().Be(BackplaneAction.Changed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_RemovedKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForRemoved(owner, key); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(null); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Invalid); + deserialized.Action.Should().Be(BackplaneAction.Removed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_RemovedKeyRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForRemoved(owner, key, region); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(key); + deserialized.Region.Should().Be(region); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Invalid); + deserialized.Action.Should().Be(BackplaneAction.Removed); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_Clear() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var msg = BackplaneMessage.ForClear(owner); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(null); + deserialized.Region.Should().Be(null); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Invalid); + deserialized.Action.Should().Be(BackplaneAction.Clear); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_ClearRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForClearRegion(owner, region); + + // act + var serialized = BackplaneMessage.Serialize(msg); + var deserialized = BackplaneMessage.Deserialize(serialized).First(); + + // assert + deserialized.Key.Should().Be(null); + deserialized.Region.Should().Be(region); + deserialized.ChangeAction.Should().Be(CacheItemChangedEventAction.Invalid); + deserialized.Action.Should().Be(BackplaneAction.ClearRegion); + deserialized.OwnerIdentity.ShouldBeEquivalentTo(owner); + deserialized.ShouldBeEquivalentTo(msg); + } + + [Fact] + public void BackplaneMessage_RefEquals() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForClearRegion(owner, region); + + // act + msg.Equals(msg).Should().BeTrue(); + } + + [Fact] + public void BackplaneMessage_EqualsOtherChangedKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key, CacheItemChangedEventAction.Update); + + // act + msg.Equals(msg2).Should().BeTrue(); + } + + [Fact] + public void BackplaneMessage_EqualsOtherChangedKeyRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + + // act + msg.Equals(msg2).Should().BeTrue(); + } + + [Fact] + public void BackplaneMessage_EqualsOtherRemovedKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForRemoved(owner, key); + var msg2 = BackplaneMessage.ForRemoved(owner, key); + + // act + msg.Equals(msg2).Should().BeTrue(); + } + + [Fact] + public void BackplaneMessage_EqualsOtherRemovedKeyRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForRemoved(owner, key, region); + var msg2 = BackplaneMessage.ForRemoved(owner, key, region); + + // act + msg.Equals(msg2).Should().BeTrue(); + } + + [Fact] + public void BackplaneMessage_EqualsOtherClearRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForClearRegion(owner, region); + var msg2 = BackplaneMessage.ForClearRegion(owner, region); + + // act + msg.Equals(msg2).Should().BeTrue(); + } + + [Fact] + public void BackplaneMessage_EqualsOtherClear() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var msg = BackplaneMessage.ForClear(owner); + var msg2 = BackplaneMessage.ForClear(owner); + + // act + msg.Equals(msg2).Should().BeTrue(); + } + + [Fact] + public void BackplaneMessage_NotEqualNull() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForClearRegion(owner, region); + + // act + msg.Equals(null).Should().BeFalse(); + } + + [Fact] + public void BackplaneMessage_NotEqualOther() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForClearRegion(owner, region); + + // act + msg.Equals("hello").Should().BeFalse(); + } + + [Fact] + public void BackplaneMessage_HashChangedKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key, CacheItemChangedEventAction.Update); + + // act + msg.GetHashCode().Should().Be(msg2.GetHashCode()); + } + + [Fact] + public void BackplaneMessage_HashChangedKeyRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + + // act + msg.GetHashCode().Should().Be(msg2.GetHashCode()); + } + + [Fact] + public void BackplaneMessage_HashRemovedKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForRemoved(owner, key); + var msg2 = BackplaneMessage.ForRemoved(owner, key); + + // act + msg.GetHashCode().Should().Be(msg2.GetHashCode()); + } + + [Fact] + public void BackplaneMessage_HashRemovedKeyRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForRemoved(owner, key, region); + var msg2 = BackplaneMessage.ForRemoved(owner, key, region); + + // act + msg.GetHashCode().Should().Be(msg2.GetHashCode()); + } + + [Fact] + public void BackplaneMessage_HashClearRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForClearRegion(owner, region); + var msg2 = BackplaneMessage.ForClearRegion(owner, region); + + // act + msg.GetHashCode().Should().Be(msg2.GetHashCode()); + } + + [Fact] + public void BackplaneMessage_HashClear() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + var msg = BackplaneMessage.ForClear(owner); + var msg2 = BackplaneMessage.ForClear(owner); + + // act + msg.GetHashCode().Should().Be(msg2.GetHashCode()); + } + + [Fact] + public void BackplaneMessage_Hashset_AllEqual() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + + var hashset = new HashSet(); + + // act + hashset.Add(msg); + hashset.Add(msg); + hashset.Count.Should().Be(1); + hashset.Add(msg2); + hashset.Add(msg2); + hashset.Count.Should().Be(1); + } + + [Fact] + public void BackplaneMessage_Hashset_DifferentChangeAction() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Add); + + var hashset = new HashSet(); + + // act + hashset.Add(msg); + hashset.Add(msg); + hashset.Count.Should().Be(1); + hashset.Add(msg2); + hashset.Add(msg2); + hashset.Count.Should().Be(2); + } + + [Fact] + public void BackplaneMessage_Hashset_DifferentKey() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var key2 = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key2, region, CacheItemChangedEventAction.Update); + + var hashset = new HashSet(); + + // act + hashset.Add(msg); + hashset.Add(msg); + hashset.Count.Should().Be(1); + hashset.Add(msg2); + hashset.Add(msg2); + hashset.Count.Should().Be(2); + } + + [Fact] + public void BackplaneMessage_Hashset_DifferentRegion() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var region2 = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForChanged(owner, key, region2, CacheItemChangedEventAction.Update); + + var hashset = new HashSet(); + + // act + hashset.Add(msg); + hashset.Add(msg); + hashset.Count.Should().Be(1); + hashset.Add(msg2); + hashset.Add(msg2); + hashset.Count.Should().Be(2); + } + + [Fact] + public void BackplaneMessage_Hashset_DifferentMessage() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + var msg = BackplaneMessage.ForChanged(owner, key, region, CacheItemChangedEventAction.Update); + var msg2 = BackplaneMessage.ForRemoved(owner, key, region); + + var hashset = new HashSet(); + + // act + hashset.Add(msg); + hashset.Add(msg); + hashset.Count.Should().Be(1); + hashset.Add(msg2); + hashset.Add(msg2); + hashset.Count.Should().Be(2); + } + + [Fact] + public void BackplaneMessage_Mulitple() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var messages = CreateMany(owner); + + var serialized = BackplaneMessage.Serialize(messages.ToArray()); + var deserialized = BackplaneMessage.Deserialize(serialized).ToArray(); + + messages.Count.Should().Be(41); + deserialized.ShouldAllBeEquivalentTo(messages); + } + + [Fact] + public void BackplaneMessage_Mulitple_IgnoreOwner() + { + // arrange + var owner = new byte[] { 1, 2, 3, 4 }; + + var messages = CreateMany(owner); + + var serialized = BackplaneMessage.Serialize(messages.ToArray()); + var deserialized = BackplaneMessage.Deserialize(serialized, skipOwner: owner).ToArray(); + + messages.Count.Should().Be(41); + deserialized.Length.Should().Be(0); + } + + private static ISet CreateMany(byte[] owner) + { + var messages = new HashSet(); + var key = Guid.NewGuid().ToString(); + var region = Guid.NewGuid().ToString(); + + // test hash compare works too, result hashset should still have 41 messages only! + for (var m = 0; m < 10000; m++) + { + for (var i = 0; i < 10; i++) + { + messages.Add(BackplaneMessage.ForChanged(owner, key + i, CacheItemChangedEventAction.Update)); + messages.Add(BackplaneMessage.ForChanged(owner, key + i, region, CacheItemChangedEventAction.Add)); + } + + messages.Add(BackplaneMessage.ForClear(owner)); + + for (var i = 0; i < 10; i++) + { + messages.Add(BackplaneMessage.ForClearRegion(owner, region + i)); + } + for (var i = 0; i < 10; i++) + { + messages.Add(BackplaneMessage.ForRemoved(owner, key + i, region)); + } + } + + return messages; + } + + [Fact] + public void BackplaneMessage_DeserializeInvalidBytes_WrongOwnerLen() + { + // arrange + var data = new byte[] { 4, 0, 1, 2, 3, 4 }; + + Action act = () => BackplaneMessage.Deserialize(data).First(); + act.ShouldThrow().WithMessage("*Cannot read bytes,*"); + } + + [Fact] + public void BackplaneMessage_DeserializeInvalidBytes_NoAction() + { + // arrange + var data = new byte[] { 4, 0, 0, 0, 1, 2, 3, 4 }; + + Action act = () => BackplaneMessage.Deserialize(data).First(); + act.ShouldThrow().WithMessage("*Cannot read byte,*"); + } + + [Fact] + public void BackplaneMessage_DeserializeInvalidBytes_WrongAction() + { + // arrange + var data = new byte[] { 4, 0, 0, 0, 1, 2, 3, 4, 255 }; + + Action act = () => BackplaneMessage.Deserialize(data).First(); + act.ShouldThrow().WithMessage("*invalid message type*"); + } + + [Fact] + public void BackplaneMessage_DeserializeInvalidBytes_InvalidString() + { + // arrange clear region with wrong region string + var data = new byte[] { 4, 0, 0, 0, 1, 2, 3, 4, 3, 10, 0, 0, 0, 42 }; + + Action act = () => BackplaneMessage.Deserialize(data).First(); + act.ShouldThrow().WithMessage("*Cannot read string,*"); + } + } +} \ No newline at end of file