From 1936b61222b99f532d94fc68d870ea25d710040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=C3=A1n=20J=C3=B6kull=20Sigur=C3=B0arson?= Date: Sun, 25 May 2025 22:25:06 +0000 Subject: [PATCH 1/6] Adding a NATS Backplane Reducing allocations for backplane messages Make Backplane Tests easily extensible --- ZiggyCreatures.FusionCache.sln | 7 + .../MemoryBackplane_Async.cs | 11 +- .../MemoryBackplane_Sync.cs | 13 +- .../NatsBackplane.cs | 144 +++++++++++++++ ...reatures.FusionCache.Backplane.NATS.csproj | 25 +++ .../artwork/logo-128x128.png | Bin 0 -> 5286 bytes .../docs/README.md | 13 ++ .../RedisBackplane.cs | 17 +- .../RedisBackplane_Async.cs | 15 +- .../RedisBackplane_Sync.cs | 17 +- .../ChaosBackplane.cs | 4 +- .../Backplane/BackplaneMessage.cs | 170 ++++++++---------- .../Backplane/IFusionCacheBackplane.cs | 2 +- .../Internals/Backplane/BackplaneAccessor.cs | 9 - .../Backplane/BackplaneAccessor_Async.cs | 9 - .../Backplane/BackplaneAccessor_Sync.cs | 13 +- .../Internals/EncodingExtensions.cs | 42 +++++ .../NullObjects/NullBackplane.cs | 2 +- .../L1BackplaneTests.cs | 85 ++++++--- .../L1BackplaneTests_Async.cs | 2 +- .../L1BackplaneTests_Sync.cs | 2 +- .../L1L2BackplaneTests.cs | 110 ++++++++---- .../L1L2BackplaneTests_Async.cs | 2 +- .../L1L2BackplaneTests_Sync.cs | 2 +- .../Stuff/LimitedCharsBackplane.cs | 4 +- .../ZiggyCreatures.FusionCache.Tests.csproj | 1 + 26 files changed, 491 insertions(+), 230 deletions(-) create mode 100644 src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs create mode 100644 src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj create mode 100644 src/ZiggyCreatures.FusionCache.Backplane.NATS/artwork/logo-128x128.png create mode 100644 src/ZiggyCreatures.FusionCache.Backplane.NATS/docs/README.md create mode 100644 src/ZiggyCreatures.FusionCache/Internals/EncodingExtensions.cs diff --git a/ZiggyCreatures.FusionCache.sln b/ZiggyCreatures.FusionCache.sln index 069e3829..1da806ba 100644 --- a/ZiggyCreatures.FusionCache.sln +++ b/ZiggyCreatures.FusionCache.sln @@ -55,6 +55,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZiggyCreatures.FusionCache. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOTTester", "tests\AOTTester\AOTTester.csproj", "{A1321882-2C76-4105-A0BD-9500D2402A2B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZiggyCreatures.FusionCache.Backplane.NATS", "src\ZiggyCreatures.FusionCache.Backplane.NATS\ZiggyCreatures.FusionCache.Backplane.NATS.csproj", "{970C789F-EEF1-08D6-6053-36CF2DCD8E68}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -141,6 +143,10 @@ Global {A1321882-2C76-4105-A0BD-9500D2402A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1321882-2C76-4105-A0BD-9500D2402A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1321882-2C76-4105-A0BD-9500D2402A2B}.Release|Any CPU.Build.0 = Release|Any CPU + {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -166,6 +172,7 @@ Global {9BC9B26A-E73F-46D4-B788-06C3A8AE63EB} = {34B53F49-F5C5-4850-B79E-59AD130379C6} {5F66F031-412F-43E3-946B-1034DBD3ED4D} = {34B53F49-F5C5-4850-B79E-59AD130379C6} {A1321882-2C76-4105-A0BD-9500D2402A2B} = {C6F3C570-C68C-4A95-960E-82778306BDBA} + {970C789F-EEF1-08D6-6053-36CF2DCD8E68} = {34B53F49-F5C5-4850-B79E-59AD130379C6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {92916FA2-FCAC-406E-BF3F-0A2CE9512EF0} diff --git a/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Async.cs b/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Async.cs index 790921ab..43735f11 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Async.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Async.cs @@ -48,9 +48,6 @@ public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOp { EnsureConnection(); - if (message is null) - throw new ArgumentNullException(nameof(message)); - if (message.IsValid() == false) throw new InvalidOperationException("The message is invalid"); @@ -60,8 +57,6 @@ public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOp if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (K={CacheKey}): [BP] about to send a backplane notification to {BackplanesCount} backplanes (including self)", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId, message.CacheKey, _subscribers.Count); - var payload = BackplaneMessage.ToByteArray(message); - foreach (var backplane in _subscribers) { token.ThrowIfCancellationRequested(); @@ -74,7 +69,7 @@ public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOp if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (K={CacheKey}): [BP] before sending a backplane notification to channel {BackplaneChannel}", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId, message.CacheKey, backplane._channelName); - await backplane.OnMessageAsync(payload).ConfigureAwait(false); + await backplane.OnMessageAsync(message).ConfigureAwait(false); if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (K={CacheKey}): [BP] after sending a backplane notification to channel {BackplaneChannel}", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId, message.CacheKey, backplane._channelName); @@ -87,10 +82,8 @@ public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOp } } - internal async ValueTask OnMessageAsync(byte[] payload) + internal async ValueTask OnMessageAsync(BackplaneMessage message) { - var message = BackplaneMessage.FromByteArray(payload); - var handler = _incomingMessageHandlerAsync; if (handler is null) diff --git a/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Sync.cs b/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Sync.cs index 66192dd5..67bf7256 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Sync.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.Memory/MemoryBackplane_Sync.cs @@ -86,13 +86,10 @@ public void Unsubscribe() } /// - public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + public void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) { EnsureConnection(); - if (message is null) - throw new ArgumentNullException(nameof(message)); - if (message.IsValid() == false) throw new InvalidOperationException("The message is invalid"); @@ -102,8 +99,6 @@ public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, C if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (K={CacheKey}): [BP] about to send a backplane notification to {BackplanesCount} backplanes (including self)", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId, message.CacheKey, _subscribers.Count); - var payload = BackplaneMessage.ToByteArray(message); - foreach (var backplane in _subscribers) { token.ThrowIfCancellationRequested(); @@ -116,7 +111,7 @@ public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, C if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (K={CacheKey}): [BP] before sending a backplane notification to channel {BackplaneChannel}", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId, message.CacheKey, backplane._channelName); - backplane.OnMessage(payload); + backplane.OnMessage(message); if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (K={CacheKey}): [BP] after sending a backplane notification to channel {BackplaneChannel}", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId, message.CacheKey, backplane._channelName); @@ -129,10 +124,8 @@ public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, C } } - internal void OnMessage(byte[] payload) + internal void OnMessage(in BackplaneMessage message) { - var message = BackplaneMessage.FromByteArray(payload); - var handler = _incomingMessageHandler; if (handler is null) diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs new file mode 100644 index 00000000..ed20e5c3 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs @@ -0,0 +1,144 @@ +using System.Buffers; +using System.Text.Json; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +using NATS.Client.Core; + +using ZiggyCreatures.Caching.Fusion.Internals; + +namespace ZiggyCreatures.Caching.Fusion.Backplane.NATS; + +/// +/// A Redis based implementation of a FusionCache backplane. +/// +public partial class NatsBackplane + : IFusionCacheBackplane +{ + private BackplaneSubscriptionOptions? _subscriptionOptions; + private readonly ILogger? _logger; + private INatsConnection _connection; + private string _channelName = ""; + private Func? _incomingMessageHandlerAsync; + private INatsSub>? _subscription; + + /// + /// Initializes a new instance of the RedisBackplane class. + /// + /// The NATS connection instance to use. + /// The instance to use. If null, logging will be completely disabled. + public NatsBackplane(INatsConnection? natsConnection, ILogger? logger = null) + { + _connection = natsConnection ?? throw new ArgumentNullException(nameof(natsConnection)); + + // LOGGING + if (logger is NullLogger) + { + // IGNORE NULL LOGGER (FOR BETTER PERF) + _logger = null; + } + else + { + _logger = logger; + } + } + + /// + public async ValueTask SubscribeAsync(BackplaneSubscriptionOptions subscriptionOptions) + { + if (subscriptionOptions is null) + throw new ArgumentNullException(nameof(subscriptionOptions)); + + if (subscriptionOptions.ChannelName is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.ChannelName cannot be null"); + + if (subscriptionOptions.IncomingMessageHandler is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.IncomingMessageHandler cannot be null"); + + if (subscriptionOptions.ConnectHandler is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.ConnectHandler cannot be null"); + + if (subscriptionOptions.IncomingMessageHandlerAsync is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.IncomingMessageHandlerAsync cannot be null"); + + if (subscriptionOptions.ConnectHandlerAsync is null) + throw new NullReferenceException("The BackplaneSubscriptionOptions.ConnectHandlerAsync cannot be null"); + + _subscriptionOptions = subscriptionOptions; + + _channelName = _subscriptionOptions.ChannelName; + if (string.IsNullOrEmpty(_channelName)) + throw new NullReferenceException("The backplane channel name must have a value"); + + _incomingMessageHandlerAsync = _subscriptionOptions.IncomingMessageHandlerAsync; + _subscription = await _connection.SubscribeCoreAsync>(_channelName); + _ = Task.Run(async () => + { + while (await _subscription.Msgs.WaitToReadAsync().ConfigureAwait(false)) + { + while (_subscription.Msgs.TryRead(out var msg)) + { + using (msg.Data) + { + if(BackplaneMessage.TryParse(msg.Data.Span, out BackplaneMessage message)) + { + await OnMessageAsync(message).ConfigureAwait(false); + } + } + } + } + }); + } + + + /// + public void Subscribe(BackplaneSubscriptionOptions options) + { + SubscribeAsync(options).AsTask().Wait(); + } + + /// + public async ValueTask UnsubscribeAsync() + { + if (_subscription is not null) + { + await _subscription.UnsubscribeAsync().ConfigureAwait(false); + await _subscription.Msgs.Completion; + } + } + + /// + public void Unsubscribe() + { + UnsubscribeAsync().AsTask().Wait(); + } + + /// + public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + { + var writer = new NatsBufferWriter(); + message.WriteTo(writer); + await _connection.PublishAsync(_channelName, writer).ConfigureAwait(false); + } + + /// + public void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + { + PublishAsync(message, options, token).AsTask().Wait(); + } + + internal async ValueTask OnMessageAsync(BackplaneMessage message) + { + var tmp = _incomingMessageHandlerAsync; + if (tmp is null) + { + if (_logger?.IsEnabled(LogLevel.Trace) ?? false) + _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}]: [BP] incoming message handler was null", _subscriptionOptions?.CacheName, _subscriptionOptions?.CacheInstanceId); + return; + } + + await tmp(message).ConfigureAwait(false); + } +} diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj b/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj new file mode 100644 index 00000000..96a301a6 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj @@ -0,0 +1,25 @@ + + + netstandard2.0 + 2.1.0 + ZiggyCreatures.FusionCache.Backplane.NATS + FusionCache backplane for NATS based on the NATS.Net library + backplane;nats;synadia;caching;cache;hybrid;hybrid-cache;hybridcache;multi-level;multilevel;fusion;fusioncache;fusion-cache;performance;async;ziggy + ZiggyCreatures.Caching.Fusion.Backplane.NATS + true + 1.0.0 + + + + + + + + + + + + + + + diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/artwork/logo-128x128.png b/src/ZiggyCreatures.FusionCache.Backplane.NATS/artwork/logo-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..ce400a79d7afa412829bf756fd31598f30250c2a GIT binary patch literal 5286 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 @@ -93,11 +95,15 @@ private void Disconnect() _connection = null; } - private static BackplaneMessage? GetMessageFromRedisValue(RedisValue value, ILogger? logger, BackplaneSubscriptionOptions? subscriptionOptions) + private static bool TryGetMessageFromRedisValue(RedisValue value, ILogger? logger, BackplaneSubscriptionOptions? subscriptionOptions, out BackplaneMessage backplaneMessage) { try { - return BackplaneMessage.FromByteArray(value); + byte[]? byteValue = value; + if (byteValue is not null && BackplaneMessage.TryParse(byteValue, out backplaneMessage)) + { + return true; + } } catch (Exception exc) { @@ -105,14 +111,17 @@ private void Disconnect() logger.Log(LogLevel.Warning, exc, "FUSION [N={CacheName} I={CacheInstanceId}]: [BP] an error occurred while converting a RedisValue into a BackplaneMessage", subscriptionOptions?.CacheName, subscriptionOptions?.CacheInstanceId); } - return null; + backplaneMessage = default; + return false; } private static RedisValue GetRedisValueFromMessage(BackplaneMessage message, ILogger? logger, BackplaneSubscriptionOptions? subscriptionOptions) { try { - return BackplaneMessage.ToByteArray(message); + using var arrayPoolBufferWriter = new ArrayPoolBufferWriter(); + message.WriteTo(arrayPoolBufferWriter); + return arrayPoolBufferWriter.ToArray(); } catch (Exception exc) { diff --git a/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Async.cs b/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Async.cs index dd000448..05f97acb 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Async.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Async.cs @@ -89,14 +89,15 @@ public async ValueTask SubscribeAsync(BackplaneSubscriptionOptions subscriptionO await _subscriber.SubscribeAsync(_channel, (rc, value) => { - var message = GetMessageFromRedisValue(value, _logger, _subscriptionOptions); - if (message is null) - return; - - _ = Task.Run(async () => + if (TryGetMessageFromRedisValue(value, _logger, _subscriptionOptions, out var message)) { - await OnMessageAsync(message).ConfigureAwait(false); - }); + _ = Task.Run(async () => + { + await OnMessageAsync(message).ConfigureAwait(false); + }); + } + + return; }).ConfigureAwait(false); } diff --git a/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Sync.cs b/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Sync.cs index 6f3ebb64..3fc1186b 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Sync.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis/RedisBackplane_Sync.cs @@ -90,14 +90,15 @@ public void Subscribe(BackplaneSubscriptionOptions subscriptionOptions) _subscriber.Subscribe(_channel, (rc, value) => { - var message = GetMessageFromRedisValue(value, _logger, _subscriptionOptions); - if (message is null) - return; - - _ = Task.Run(async () => + if (TryGetMessageFromRedisValue(value, _logger, _subscriptionOptions, out var message)) { - await OnMessageAsync(message).ConfigureAwait(false); - }); + _ = Task.Run(async () => + { + await OnMessageAsync(message).ConfigureAwait(false); + }); + } + + return; }); } @@ -116,7 +117,7 @@ public void Unsubscribe() } /// - public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + public void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) { // CONNECTION EnsureConnection(token); diff --git a/src/ZiggyCreatures.FusionCache.Chaos/ChaosBackplane.cs b/src/ZiggyCreatures.FusionCache.Chaos/ChaosBackplane.cs index bb250707..bec3d76e 100644 --- a/src/ZiggyCreatures.FusionCache.Chaos/ChaosBackplane.cs +++ b/src/ZiggyCreatures.FusionCache.Chaos/ChaosBackplane.cs @@ -113,10 +113,10 @@ public async ValueTask UnsubscribeAsync() } /// - public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + public void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) { MaybeChaos(token); - _innerBackplane.Publish(message, options, token); + _innerBackplane.Publish(in message, options, token); } /// diff --git a/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs b/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs index aa2f9a0c..7ce9e55f 100644 --- a/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs +++ b/src/ZiggyCreatures.FusionCache/Backplane/BackplaneMessage.cs @@ -1,4 +1,6 @@ -using System.Buffers.Binary; +using System.Buffers; +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; using System.Text; using ZiggyCreatures.Caching.Fusion.Internals; @@ -7,46 +9,44 @@ namespace ZiggyCreatures.Caching.Fusion.Backplane; /// /// Represents a message on a backplane. /// -public class BackplaneMessage +public readonly struct BackplaneMessage { private static readonly Encoding _encoding = Encoding.UTF8; /// /// Creates a new instance of a backplane message. /// - public BackplaneMessage() - { - Timestamp = FusionCacheInternalUtils.GetCurrentTimestamp(); - } - - /// - /// Creates a new instance of a backplane message. - /// + /// The action to broadcast to the backplane. /// The timestamp (in ticks) related to the operation being notified. - public BackplaneMessage(long timestamp) + /// The cache InstanceId of the source. + /// The cache key related to the action, if any. + private BackplaneMessage(BackplaneMessageAction action, long timestamp, string sourceId, string cacheKey) { + Action = action; Timestamp = timestamp; + SourceId = sourceId; + CacheKey = cacheKey; } /// /// The InstanceId of the source cache. /// - public string? SourceId { get; set; } + public readonly string SourceId; /// /// The timestamp (in ticks) related to the operation being notified. /// - public long Timestamp { get; set; } + public readonly long Timestamp; /// /// The action to broadcast to the backplane. /// - public BackplaneMessageAction Action { get; set; } + public readonly BackplaneMessageAction Action; /// /// The cache key related to the action, if any. /// - public string? CacheKey { get; set; } + public readonly string CacheKey; /// /// Checks if a message is valid. @@ -85,12 +85,7 @@ public static BackplaneMessage CreateForEntrySet(string sourceId, string cacheKe if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentException("The cache key cannot be null or empty", nameof(cacheKey)); - return new BackplaneMessage(timestamp) - { - SourceId = sourceId, - Action = BackplaneMessageAction.EntrySet, - CacheKey = cacheKey - }; + return new BackplaneMessage(BackplaneMessageAction.EntrySet, timestamp, sourceId, cacheKey); } /// @@ -105,12 +100,7 @@ public static BackplaneMessage CreateForEntryRemove(string sourceId, string cach if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentException("The cache key cannot be null or empty", nameof(cacheKey)); - return new BackplaneMessage(timestamp) - { - SourceId = sourceId, - Action = BackplaneMessageAction.EntryRemove, - CacheKey = cacheKey - }; + return new BackplaneMessage(BackplaneMessageAction.EntryRemove, timestamp, sourceId, cacheKey); } /// @@ -125,30 +115,19 @@ public static BackplaneMessage CreateForEntryExpire(string sourceId, string cach if (string.IsNullOrEmpty(cacheKey)) throw new ArgumentException("The cache key cannot be null or empty", nameof(cacheKey)); - return new BackplaneMessage(timestamp) - { - SourceId = sourceId, - Action = BackplaneMessageAction.EntryExpire, - CacheKey = cacheKey - }; + return new BackplaneMessage(BackplaneMessageAction.EntryExpire, timestamp, sourceId, cacheKey); } /// - /// Serializes a backplane message to a byte array. + /// Writes the backplane message to a buffer writer. /// - /// The backplane message to serialize. - /// The message as a byte[]. - public static byte[] ToByteArray(BackplaneMessage? message) + /// The type of the buffer writer. + /// The buffer writer to write to. + /// + public void WriteTo(T writer) where T : IBufferWriter { - if (message is null) - throw new ArgumentNullException(nameof(message)); - if (message.SourceId is null) - throw new ArgumentNullException(nameof(message.SourceId)); - if (message.CacheKey is null) - throw new ArgumentNullException(nameof(message.CacheKey)); - - var sourceIdByteCount = _encoding.GetByteCount(message.SourceId); - var cacheKeyByteCount = _encoding.GetByteCount(message.CacheKey); + var sourceIdByteCount = _encoding.GetByteCount(SourceId); + var cacheKeyByteCount = _encoding.GetByteCount(CacheKey); var size = 1 // VERSION @@ -158,79 +137,70 @@ public static byte[] ToByteArray(BackplaneMessage? message) + 4 + cacheKeyByteCount // CACHE KEY ; - var res = new byte[size]; - var pos = 0; + var span = writer.GetSpan(size); // VERSION - res[pos] = 0; - pos++; + span[0] = 0; // SOURCE ID - BinaryPrimitives.WriteInt32LittleEndian(new Span(res, pos, 4), sourceIdByteCount); - pos += 4; - _encoding.GetBytes(message.SourceId!, 0, message.SourceId!.Length, res, pos); - pos += sourceIdByteCount; + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(1, 4), sourceIdByteCount); + _encoding.GetBytes(SourceId, span.Slice(5)); // TIMESTAMP - BinaryPrimitives.WriteInt64LittleEndian(new Span(res, pos, 8), message.Timestamp); - pos += 8; + BinaryPrimitives.WriteInt64LittleEndian(span.Slice(5 + sourceIdByteCount, 8), Timestamp); // ACTION - res[pos] = (byte)message.Action; - pos++; + span[13 + sourceIdByteCount] = (byte)Action; // CACHE KEY - BinaryPrimitives.WriteInt32LittleEndian(new Span(res, pos, 4), cacheKeyByteCount); - pos += 4; - _encoding.GetBytes(message.CacheKey, 0, message.CacheKey!.Length, res, pos); - //pos += cacheKeyByteCount; - - return res; + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(14 + sourceIdByteCount, 4), cacheKeyByteCount); + _encoding.GetBytes(CacheKey, span.Slice(18 + sourceIdByteCount)); + writer.Advance(size); } /// - /// Deserializes a byte array into a backplane message. + /// Tries to parse a byte array into a backplane message. /// - /// The byte array to deserialize. - /// An instance of a backplane message, or - /// - public static BackplaneMessage FromByteArray(byte[]? data) + /// The byte array to parse. + /// When successful, the parsed backplane message. + /// True if the parsing was successful, false otherwise. + public static bool TryParse(ReadOnlySpan data, out BackplaneMessage message) { - if (data is null) - throw new ArgumentNullException(nameof(data)); - - if (data.Length == 0) - throw new InvalidOperationException("The byte array is empty."); - - var res = new BackplaneMessage(); - var pos = 0; - - // VERSION - var version = data[pos]; - if (version != 0) - throw new FormatException($"The backplane message version ({version}) is not supported."); - pos++; + if (data.IsEmpty) + { + message = default; + return false; + } - // SOURCE ID - var tmp = BinaryPrimitives.ReadInt32LittleEndian(new ReadOnlySpan(data, pos, 4)); - pos += 4; - res.SourceId = _encoding.GetString(data, pos, tmp); - pos += tmp; + // Check the version + if (data[0] != 0) + { + message = default; + return false; + } - // TIMESTAMP - res.Timestamp = BinaryPrimitives.ReadInt64LittleEndian(new ReadOnlySpan(data, pos, 8)); - pos += 8; + try + { + // SOURCE ID + int sourceIdLength = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(1)); + string sourceId = _encoding.GetString(data.Slice(5, sourceIdLength)); - // ACTION - res.Action = (BackplaneMessageAction)data[pos]; - pos++; + // TIMESTAMP + long timestamp = BinaryPrimitives.ReadInt64LittleEndian(data.Slice(5 + sourceIdLength)); - // CACHE KEY - tmp = BinaryPrimitives.ReadInt32LittleEndian(new ReadOnlySpan(data, pos, 4)); - pos += 4; - res.CacheKey = _encoding.GetString(data, pos, tmp); - //pos += tmp; + // ACTION + BackplaneMessageAction backplaneMessageAction = (BackplaneMessageAction)data[13 + sourceIdLength]; - return res; + // CACHE KEY + int cacheKeyLength = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(14 + sourceIdLength)); + string cacheKey = _encoding.GetString(data.Slice(18 + sourceIdLength, cacheKeyLength)); + message = new BackplaneMessage(backplaneMessageAction, timestamp, sourceId, cacheKey); + return true; + } + catch (Exception) + { + message = default; + return false; + } } } diff --git a/src/ZiggyCreatures.FusionCache/Backplane/IFusionCacheBackplane.cs b/src/ZiggyCreatures.FusionCache/Backplane/IFusionCacheBackplane.cs index 07b3a92c..fe9e1fc7 100644 --- a/src/ZiggyCreatures.FusionCache/Backplane/IFusionCacheBackplane.cs +++ b/src/ZiggyCreatures.FusionCache/Backplane/IFusionCacheBackplane.cs @@ -41,5 +41,5 @@ public interface IFusionCacheBackplane /// The message to send. /// The options to use. /// An optional to cancel the operation. - void Publish(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default); + void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default); } diff --git a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor.cs b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor.cs index 5d64b656..d81961c2 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor.cs @@ -90,15 +90,6 @@ private void ProcessError(string operationId, string key, Exception exc, string private bool CheckMessage(string operationId, BackplaneMessage message, bool isAutoRecovery) { - // CHECK: IGNORE NULL - if (message is null) - { - if (_logger?.IsEnabled(_options.BackplaneErrorsLogLevel) ?? false) - _logger.Log(_options.BackplaneErrorsLogLevel, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId}): [BP] cannot send a null backplane message (what!?)", _cache.CacheName, _cache.InstanceId, operationId); - - return false; - } - // CHECK: IS VALID if (message.IsValid() == false) { diff --git a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs index a22a1e03..35c6fd87 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Async.cs @@ -210,15 +210,6 @@ private async ValueTask HandleIncomingMessageAsync(BackplaneMessage message) var operationId = FusionCacheInternalUtils.MaybeGenerateOperationId(_logger); - // IGNORE NULL - if (message is null) - { - if (_logger?.IsEnabled(_options.BackplaneErrorsLogLevel) ?? false) - _logger.Log(_options.BackplaneErrorsLogLevel, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId}): [BP] a null backplane notification has been received (what!?)", _cache.CacheName, _cache.InstanceId, operationId); - - return; - } - // IGNORE MESSAGES FROM THIS SOURCE if (message.SourceId == _cache.InstanceId) { diff --git a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs index 2841e619..9603ea21 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Backplane/BackplaneAccessor_Sync.cs @@ -91,7 +91,7 @@ public void Unsubscribe() } } - private bool Publish(string operationId, BackplaneMessage message, FusionCacheEntryOptions options, bool isAutoRecovery, bool isBackground, CancellationToken token) + private bool Publish(string operationId, in BackplaneMessage message, FusionCacheEntryOptions options, bool isAutoRecovery, bool isBackground, CancellationToken token) { if (CheckMessage(operationId, message, isAutoRecovery) == false) return false; @@ -122,7 +122,7 @@ private bool Publish(string operationId, BackplaneMessage message, FusionCacheEn if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId} K={CacheKey}): [BP] before " + actionDescription, _options.CacheName, _options.InstanceId, operationId, cacheKey); - _backplane.Publish(message, options, token); + _backplane.Publish(in message, options, token); // EVENT _events.OnMessagePublished(operationId, message); @@ -210,15 +210,6 @@ private void HandleIncomingMessage(BackplaneMessage message) var operationId = FusionCacheInternalUtils.MaybeGenerateOperationId(_logger); - // IGNORE NULL - if (message is null) - { - if (_logger?.IsEnabled(_options.BackplaneErrorsLogLevel) ?? false) - _logger.Log(_options.BackplaneErrorsLogLevel, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId}): [BP] a null backplane notification has been received (what!?)", _cache.CacheName, _cache.InstanceId, operationId); - - return; - } - // IGNORE MESSAGES FROM THIS SOURCE if (message.SourceId == _cache.InstanceId) { diff --git a/src/ZiggyCreatures.FusionCache/Internals/EncodingExtensions.cs b/src/ZiggyCreatures.FusionCache/Internals/EncodingExtensions.cs new file mode 100644 index 00000000..40e4f5c2 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Internals/EncodingExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Text; + +namespace ZiggyCreatures.Caching.Fusion.Internals +{ + internal static class EncodingExtensions + { +#if NETSTANDARD2_0 + public static int GetBytes(this T encoding, string s, Span span) where T : Encoding + { + int byteCount = encoding.GetByteCount(s); + byte[] stringBytes = ArrayPool.Shared.Rent(byteCount); + try + { + encoding.GetBytes(s, 0, s.Length, stringBytes, 0); + stringBytes.AsSpan(0, byteCount).CopyTo(span); + return byteCount; + } + finally + { + ArrayPool.Shared.Return(stringBytes); + } + } + + public static string GetString(this T encoding, ReadOnlySpan bytes) where T : Encoding + { + byte[] stringBytes = ArrayPool.Shared.Rent(bytes.Length); + try + { + bytes.CopyTo(stringBytes); + return encoding.GetString(stringBytes, 0, bytes.Length); + } + finally + { + ArrayPool.Shared.Return(stringBytes); + } + } +#endif + } +} diff --git a/src/ZiggyCreatures.FusionCache/NullObjects/NullBackplane.cs b/src/ZiggyCreatures.FusionCache/NullObjects/NullBackplane.cs index a58f0c36..871147d4 100644 --- a/src/ZiggyCreatures.FusionCache/NullObjects/NullBackplane.cs +++ b/src/ZiggyCreatures.FusionCache/NullObjects/NullBackplane.cs @@ -33,7 +33,7 @@ public ValueTask UnsubscribeAsync() } /// - public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + public void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) { // EMPTY } diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs index d8baed35..3a12a488 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests.cs @@ -1,26 +1,29 @@ using FusionCacheTests.Stuff; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; + +using NATS.Client.Core; + using Xunit; using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion.Backplane; using ZiggyCreatures.Caching.Fusion.Backplane.Memory; +using ZiggyCreatures.Caching.Fusion.Backplane.NATS; using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.DangerZone; namespace FusionCacheTests; -public partial class L1BackplaneTests - : AbstractTests +public abstract partial class L1BackplaneTests : AbstractTests where T : IFusionCacheBackplane { - public L1BackplaneTests(ITestOutputHelper output) - : base(output, "MyCache:") - { - if (UseRedis) - InitialBackplaneDelay = TimeSpan.FromSeconds(5).PlusALittleBit(); + protected TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); + protected TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); + + protected L1BackplaneTests(ITestOutputHelper output) : base(output, "MyCache:") + { } - private FusionCacheOptions CreateFusionCacheOptions() + protected virtual FusionCacheOptions CreateFusionCacheOptions() { var res = new FusionCacheOptions { @@ -32,21 +35,9 @@ private FusionCacheOptions CreateFusionCacheOptions() return res; } - private static readonly bool UseRedis = false; - private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; - - private readonly TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); - private readonly TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); - - private IFusionCacheBackplane CreateBackplane(string connectionId, ILogger? logger = null) - { - if (UseRedis) - return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: (logger as ILogger) ?? CreateXUnitLogger()); - - return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: (logger as ILogger) ?? CreateXUnitLogger()); - } + protected abstract T CreateBackplane(string connectionId, ILogger? logger = null); - private FusionCache CreateFusionCache(string? cacheName, IFusionCacheBackplane? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null, ILogger? logger = null) + protected FusionCache CreateFusionCache(string? cacheName, T? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null, ILogger? logger = null) { var options = CreateFusionCacheOptions(); @@ -67,3 +58,53 @@ private FusionCache CreateFusionCache(string? cacheName, IFusionCacheBackplane? return fusionCache; } } + +public class RedisL1BackplaneTests : L1BackplaneTests +{ + private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; + + public RedisL1BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override RedisBackplane CreateBackplane(string connectionId, ILogger? logger = null) + { + return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: (logger as ILogger) ?? CreateXUnitLogger()); + } +} + +public class MemoryL1BackplaneTests : L1BackplaneTests +{ + public MemoryL1BackplaneTests(ITestOutputHelper output) : base(output) + { + } + + protected override MemoryBackplane CreateBackplane(string connectionId, ILogger? logger = null) + { + return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: (logger as ILogger) ?? CreateXUnitLogger()); + } +} + +public class NatsL1BackplaneTests : L1BackplaneTests +{ + private static readonly string NatsConnection = "nats://localhost:4222"; + + public NatsL1BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override FusionCacheOptions CreateFusionCacheOptions() + { + var options = base.CreateFusionCacheOptions(); + options.InternalStrings.SetToSafeStrings(); + return options; + } + + protected override NatsBackplane CreateBackplane(string connectionId, ILogger? logger = null) + { + var natsConnection = new NatsConnection(new NatsOpts() { Url = NatsConnection }); + return new NatsBackplane(natsConnection, logger: (logger as ILogger) ?? CreateXUnitLogger()); + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs index 2b9130ef..82885c82 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Async.cs @@ -8,7 +8,7 @@ namespace FusionCacheTests; -public partial class L1BackplaneTests +public partial class L1BackplaneTests { [Fact] public async Task WorksWithDifferentCachesAsync() diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs index 01015744..542ff0d7 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1BackplaneTests_Sync.cs @@ -8,7 +8,7 @@ namespace FusionCacheTests; -public partial class L1BackplaneTests +public partial class L1BackplaneTests { [Fact] public void WorksWithDifferentCaches() diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs index 491c5aff..34ec4d85 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests.cs @@ -3,26 +3,29 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Options; + +using NATS.Client.Core; + using Xunit; using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion.Backplane; using ZiggyCreatures.Caching.Fusion.Backplane.Memory; +using ZiggyCreatures.Caching.Fusion.Backplane.NATS; using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.DangerZone; namespace FusionCacheTests; -public partial class L1L2BackplaneTests - : AbstractTests +public abstract partial class L1L2BackplaneTests : AbstractTests where TBackplane : class, IFusionCacheBackplane where TDistributedCache : IDistributedCache { - public L1L2BackplaneTests(ITestOutputHelper output) - : base(output, "MyCache:") + protected TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); + protected TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); + + protected L1L2BackplaneTests(ITestOutputHelper output) : base(output, "MyCache:") { - if (UseRedis) - InitialBackplaneDelay = TimeSpan.FromSeconds(5).PlusALittleBit(); } - private FusionCacheOptions CreateFusionCacheOptions() + protected virtual FusionCacheOptions CreateFusionCacheOptions() { var res = new FusionCacheOptions { @@ -33,30 +36,11 @@ private FusionCacheOptions CreateFusionCacheOptions() return res; } - - private static readonly bool UseRedis = false; - private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; - - private readonly TimeSpan InitialBackplaneDelay = TimeSpan.FromMilliseconds(300); - private readonly TimeSpan MultiNodeOperationsDelay = TimeSpan.FromMilliseconds(300); - - private IFusionCacheBackplane CreateBackplane(string connectionId) - { - if (UseRedis) - return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: CreateXUnitLogger()); - - return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: CreateXUnitLogger()); - } - - private static IDistributedCache CreateDistributedCache() - { - if (UseRedis) - return new RedisCache(new RedisCacheOptions { Configuration = RedisConnection }); - - return new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - } - - private FusionCache CreateFusionCache(string? cacheName, SerializerType? serializerType, IDistributedCache? distributedCache, IFusionCacheBackplane? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null) + + protected abstract TBackplane CreateBackplane(string connectionId); + protected abstract TDistributedCache CreateDistributedCache(); + + protected FusionCache CreateFusionCache(string? cacheName, SerializerType? serializerType, TDistributedCache? distributedCache, TBackplane? backplane, Action? setupAction = null, IMemoryCache? memoryCache = null, string? cacheInstanceId = null) { var options = CreateFusionCacheOptions(); @@ -83,3 +67,67 @@ private FusionCache CreateFusionCache(string? cacheName, SerializerType? seriali return fusionCache; } } + +public class RedisL1L2BackplaneTests : L1L2BackplaneTests +{ + private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; + + public RedisL1L2BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override RedisBackplane CreateBackplane(string connectionId) + { + return new RedisBackplane(new RedisBackplaneOptions { Configuration = RedisConnection }, logger: CreateXUnitLogger()); + } + + protected override RedisCache CreateDistributedCache() + { + return new RedisCache(new RedisCacheOptions { Configuration = RedisConnection }); + } +} + +public class MemoryL1L2BackplaneTests : L1L2BackplaneTests +{ + public MemoryL1L2BackplaneTests(ITestOutputHelper output) : base(output) + { + } + protected override MemoryBackplane CreateBackplane(string connectionId) + { + return new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = connectionId }, logger: CreateXUnitLogger()); + } + protected override MemoryDistributedCache CreateDistributedCache() + { + return new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); + } +} + +public class NatsL1L2BackplaneTests : L1L2BackplaneTests +{ + private static readonly string NatsConnection = "nats://localhost:4222"; + private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; + + public NatsL1L2BackplaneTests(ITestOutputHelper output) : base(output) + { + InitialBackplaneDelay = TimeSpan.FromSeconds(1).PlusALittleBit(); + } + + protected override FusionCacheOptions CreateFusionCacheOptions() + { + var options = base.CreateFusionCacheOptions(); + options.InternalStrings.SetToSafeStrings(); + return options; + } + + protected override NatsBackplane CreateBackplane(string connectionId) + { + var natsConnection = new NatsConnection(new NatsOpts() { Url = NatsConnection }); + return new NatsBackplane(natsConnection, logger: CreateXUnitLogger()); + } + + protected override RedisCache CreateDistributedCache() + { + return new RedisCache(new RedisCacheOptions { Configuration = RedisConnection }); + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs index 16e9d25f..e293dc9e 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs @@ -14,7 +14,7 @@ namespace FusionCacheTests; -public partial class L1L2BackplaneTests +public partial class L1L2BackplaneTests { [Theory] [ClassData(typeof(SerializerTypesClassData))] diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs index 7e9bee11..4e8ffafe 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs @@ -14,7 +14,7 @@ namespace FusionCacheTests; -public partial class L1L2BackplaneTests +public partial class L1L2BackplaneTests { [Theory] [ClassData(typeof(SerializerTypesClassData))] diff --git a/tests/ZiggyCreatures.FusionCache.Tests/Stuff/LimitedCharsBackplane.cs b/tests/ZiggyCreatures.FusionCache.Tests/Stuff/LimitedCharsBackplane.cs index 4b54dbab..95538975 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/Stuff/LimitedCharsBackplane.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/Stuff/LimitedCharsBackplane.cs @@ -46,9 +46,9 @@ public async ValueTask UnsubscribeAsync() await _innerBackplane.UnsubscribeAsync(); } - public void Publish(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) + public void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) { - _innerBackplane.Publish(message, options, token); + _innerBackplane.Publish(in message, options, token); } public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) diff --git a/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj b/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj index c3423dea..3da7cc67 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj +++ b/tests/ZiggyCreatures.FusionCache.Tests/ZiggyCreatures.FusionCache.Tests.csproj @@ -30,6 +30,7 @@ + From 1d5a3c56e63050e221b97bc9d8c8730196c05517 Mon Sep 17 00:00:00 2001 From: Jody Donetti Date: Tue, 27 May 2025 13:56:35 +0200 Subject: [PATCH 2/6] Update src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs Co-authored-by: Fabio Spaziani --- src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs index ed20e5c3..07a0daa7 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs @@ -25,7 +25,7 @@ public partial class NatsBackplane private INatsSub>? _subscription; /// - /// Initializes a new instance of the RedisBackplane class. + /// Initializes a new instance of the NatsBackplane class. /// /// The NATS connection instance to use. /// The instance to use. If null, logging will be completely disabled. From d86b553078643c787f4f358944d813a39305b17f Mon Sep 17 00:00:00 2001 From: Jody Donetti Date: Tue, 27 May 2025 13:56:45 +0200 Subject: [PATCH 3/6] Update src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs Co-authored-by: Fabio Spaziani --- src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs index 07a0daa7..6688e9e7 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs @@ -12,7 +12,7 @@ namespace ZiggyCreatures.Caching.Fusion.Backplane.NATS; /// -/// A Redis based implementation of a FusionCache backplane. +/// A NATS based implementation of a FusionCache backplane. /// public partial class NatsBackplane : IFusionCacheBackplane From 649f81bef9d6958d7fdefc6ffef4af7b0cba7c17 Mon Sep 17 00:00:00 2001 From: Jody Donetti Date: Sat, 18 Oct 2025 23:16:50 +0200 Subject: [PATCH 4/6] Switched to SLNX format --- ZiggyCreatures.FusionCache.sln | 180 -------------------------------- ZiggyCreatures.FusionCache.slnx | 33 ++++++ 2 files changed, 33 insertions(+), 180 deletions(-) delete mode 100644 ZiggyCreatures.FusionCache.sln create mode 100644 ZiggyCreatures.FusionCache.slnx diff --git a/ZiggyCreatures.FusionCache.sln b/ZiggyCreatures.FusionCache.sln deleted file mode 100644 index 1da806ba..00000000 --- a/ZiggyCreatures.FusionCache.sln +++ /dev/null @@ -1,180 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32630.192 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{34B53F49-F5C5-4850-B79E-59AD130379C6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache", "src\ZiggyCreatures.FusionCache\ZiggyCreatures.FusionCache.csproj", "{8EA21069-A6E8-4DAB-8368-796BAA10B40E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{407E264A-43D3-4139-BF62-D505EECA36F8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C6F3C570-C68C-4A95-960E-82778306BDBA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson", "src\ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson\ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson.csproj", "{482379B4-2A21-4152-B84E-554006FCAC95}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Serialization.SystemTextJson", "src\ZiggyCreatures.FusionCache.Serialization.SystemTextJson\ZiggyCreatures.FusionCache.Serialization.SystemTextJson.csproj", "{ED5B2080-C706-4150-897D-59482F41BC34}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Benchmarks", "benchmarks\ZiggyCreatures.FusionCache.Benchmarks\ZiggyCreatures.FusionCache.Benchmarks.csproj", "{EA8020A5-B936-415A-8A1B-0DC8CA7914B1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Tests", "tests\ZiggyCreatures.FusionCache.Tests\ZiggyCreatures.FusionCache.Tests.csproj", "{25C0D76C-E7D2-43CC-8D0E-BECB0438527B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BDC18C05-F0DF-4B40-90D8-4BE559EA0C23}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - Directory.Build.props = Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Playground", "tests\ZiggyCreatures.FusionCache.Playground\ZiggyCreatures.FusionCache.Playground.csproj", "{7063CEBE-CE42-4E4D-8EE6-EB719CEF2F2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Backplane.Memory", "src\ZiggyCreatures.FusionCache.Backplane.Memory\ZiggyCreatures.FusionCache.Backplane.Memory.csproj", "{AEBFD35A-48BF-45ED-8AB4-F9DB1882D12D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis", "src\ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis\ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis.csproj", "{5659A831-8170-4398-A130-D521844BB4C0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Chaos", "src\ZiggyCreatures.FusionCache.Chaos\ZiggyCreatures.FusionCache.Chaos.csproj", "{6DC66BBC-6B35-4CCC-A48B-6AD28C336C80}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack", "src\ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack\ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack.csproj", "{DD0A6164-CE88-49A0-AF4E-7A56ACBB13BB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Serialization.ProtoBufNet", "src\ZiggyCreatures.FusionCache.Serialization.ProtoBufNet\ZiggyCreatures.FusionCache.Serialization.ProtoBufNet.csproj", "{FC2C7F03-69E0-4CCE-A582-1FB6D8D11560}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack", "src\ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack\ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack.csproj", "{919CDF8C-463A-4E82-AFFD-DF8A6B904600}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Serialization.ServiceStackJson", "src\ZiggyCreatures.FusionCache.Serialization.ServiceStackJson\ZiggyCreatures.FusionCache.Serialization.ServiceStackJson.csproj", "{CE437FB2-510F-4DCE-8A1F-AED747DAA4EB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerializerPayloadGenerator", "tests\SerializerPayloadGenerator\SerializerPayloadGenerator.csproj", "{5B1AF24E-90FC-4C21-AF9C-090FE32027E3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.Simulator", "tests\ZiggyCreatures.FusionCache.Simulator\ZiggyCreatures.FusionCache.Simulator.csproj", "{BDB46997-84D1-4CB5-B967-7F820820CB8E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZiggyCreatures.FusionCache.OpenTelemetry", "src\ZiggyCreatures.FusionCache.OpenTelemetry\ZiggyCreatures.FusionCache.OpenTelemetry.csproj", "{DA78EB72-93B1-4A77-8525-79AF3EEC4C8D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppTest", "tests\WebAppTest\WebAppTest.csproj", "{B9E04359-B835-443C-8D04-BBDD03F00A89}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZiggyCreatures.FusionCache.AspNetCore.OutputCaching", "src\ZiggyCreatures.FusionCache.AspNetCore.OutputCaching\ZiggyCreatures.FusionCache.AspNetCore.OutputCaching.csproj", "{9BC9B26A-E73F-46D4-B788-06C3A8AE63EB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZiggyCreatures.FusionCache.Locking.AsyncKeyed", "src\ZiggyCreatures.FusionCache.Locking.AsyncKeyed\ZiggyCreatures.FusionCache.Locking.AsyncKeyed.csproj", "{5F66F031-412F-43E3-946B-1034DBD3ED4D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOTTester", "tests\AOTTester\AOTTester.csproj", "{A1321882-2C76-4105-A0BD-9500D2402A2B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZiggyCreatures.FusionCache.Backplane.NATS", "src\ZiggyCreatures.FusionCache.Backplane.NATS\ZiggyCreatures.FusionCache.Backplane.NATS.csproj", "{970C789F-EEF1-08D6-6053-36CF2DCD8E68}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8EA21069-A6E8-4DAB-8368-796BAA10B40E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8EA21069-A6E8-4DAB-8368-796BAA10B40E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8EA21069-A6E8-4DAB-8368-796BAA10B40E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8EA21069-A6E8-4DAB-8368-796BAA10B40E}.Release|Any CPU.Build.0 = Release|Any CPU - {482379B4-2A21-4152-B84E-554006FCAC95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {482379B4-2A21-4152-B84E-554006FCAC95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {482379B4-2A21-4152-B84E-554006FCAC95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {482379B4-2A21-4152-B84E-554006FCAC95}.Release|Any CPU.Build.0 = Release|Any CPU - {ED5B2080-C706-4150-897D-59482F41BC34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ED5B2080-C706-4150-897D-59482F41BC34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ED5B2080-C706-4150-897D-59482F41BC34}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ED5B2080-C706-4150-897D-59482F41BC34}.Release|Any CPU.Build.0 = Release|Any CPU - {EA8020A5-B936-415A-8A1B-0DC8CA7914B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA8020A5-B936-415A-8A1B-0DC8CA7914B1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA8020A5-B936-415A-8A1B-0DC8CA7914B1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA8020A5-B936-415A-8A1B-0DC8CA7914B1}.Release|Any CPU.Build.0 = Release|Any CPU - {25C0D76C-E7D2-43CC-8D0E-BECB0438527B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25C0D76C-E7D2-43CC-8D0E-BECB0438527B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25C0D76C-E7D2-43CC-8D0E-BECB0438527B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25C0D76C-E7D2-43CC-8D0E-BECB0438527B}.Release|Any CPU.Build.0 = Release|Any CPU - {7063CEBE-CE42-4E4D-8EE6-EB719CEF2F2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7063CEBE-CE42-4E4D-8EE6-EB719CEF2F2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7063CEBE-CE42-4E4D-8EE6-EB719CEF2F2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7063CEBE-CE42-4E4D-8EE6-EB719CEF2F2F}.Release|Any CPU.Build.0 = Release|Any CPU - {AEBFD35A-48BF-45ED-8AB4-F9DB1882D12D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AEBFD35A-48BF-45ED-8AB4-F9DB1882D12D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AEBFD35A-48BF-45ED-8AB4-F9DB1882D12D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AEBFD35A-48BF-45ED-8AB4-F9DB1882D12D}.Release|Any CPU.Build.0 = Release|Any CPU - {5659A831-8170-4398-A130-D521844BB4C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5659A831-8170-4398-A130-D521844BB4C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5659A831-8170-4398-A130-D521844BB4C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5659A831-8170-4398-A130-D521844BB4C0}.Release|Any CPU.Build.0 = Release|Any CPU - {6DC66BBC-6B35-4CCC-A48B-6AD28C336C80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DC66BBC-6B35-4CCC-A48B-6AD28C336C80}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DC66BBC-6B35-4CCC-A48B-6AD28C336C80}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DC66BBC-6B35-4CCC-A48B-6AD28C336C80}.Release|Any CPU.Build.0 = Release|Any CPU - {DD0A6164-CE88-49A0-AF4E-7A56ACBB13BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DD0A6164-CE88-49A0-AF4E-7A56ACBB13BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DD0A6164-CE88-49A0-AF4E-7A56ACBB13BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DD0A6164-CE88-49A0-AF4E-7A56ACBB13BB}.Release|Any CPU.Build.0 = Release|Any CPU - {FC2C7F03-69E0-4CCE-A582-1FB6D8D11560}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC2C7F03-69E0-4CCE-A582-1FB6D8D11560}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC2C7F03-69E0-4CCE-A582-1FB6D8D11560}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC2C7F03-69E0-4CCE-A582-1FB6D8D11560}.Release|Any CPU.Build.0 = Release|Any CPU - {919CDF8C-463A-4E82-AFFD-DF8A6B904600}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {919CDF8C-463A-4E82-AFFD-DF8A6B904600}.Debug|Any CPU.Build.0 = Debug|Any CPU - {919CDF8C-463A-4E82-AFFD-DF8A6B904600}.Release|Any CPU.ActiveCfg = Release|Any CPU - {919CDF8C-463A-4E82-AFFD-DF8A6B904600}.Release|Any CPU.Build.0 = Release|Any CPU - {CE437FB2-510F-4DCE-8A1F-AED747DAA4EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE437FB2-510F-4DCE-8A1F-AED747DAA4EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE437FB2-510F-4DCE-8A1F-AED747DAA4EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE437FB2-510F-4DCE-8A1F-AED747DAA4EB}.Release|Any CPU.Build.0 = Release|Any CPU - {5B1AF24E-90FC-4C21-AF9C-090FE32027E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B1AF24E-90FC-4C21-AF9C-090FE32027E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B1AF24E-90FC-4C21-AF9C-090FE32027E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B1AF24E-90FC-4C21-AF9C-090FE32027E3}.Release|Any CPU.Build.0 = Release|Any CPU - {BDB46997-84D1-4CB5-B967-7F820820CB8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BDB46997-84D1-4CB5-B967-7F820820CB8E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BDB46997-84D1-4CB5-B967-7F820820CB8E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BDB46997-84D1-4CB5-B967-7F820820CB8E}.Release|Any CPU.Build.0 = Release|Any CPU - {DA78EB72-93B1-4A77-8525-79AF3EEC4C8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA78EB72-93B1-4A77-8525-79AF3EEC4C8D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA78EB72-93B1-4A77-8525-79AF3EEC4C8D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA78EB72-93B1-4A77-8525-79AF3EEC4C8D}.Release|Any CPU.Build.0 = Release|Any CPU - {B9E04359-B835-443C-8D04-BBDD03F00A89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9E04359-B835-443C-8D04-BBDD03F00A89}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9E04359-B835-443C-8D04-BBDD03F00A89}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9E04359-B835-443C-8D04-BBDD03F00A89}.Release|Any CPU.Build.0 = Release|Any CPU - {9BC9B26A-E73F-46D4-B788-06C3A8AE63EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BC9B26A-E73F-46D4-B788-06C3A8AE63EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BC9B26A-E73F-46D4-B788-06C3A8AE63EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BC9B26A-E73F-46D4-B788-06C3A8AE63EB}.Release|Any CPU.Build.0 = Release|Any CPU - {5F66F031-412F-43E3-946B-1034DBD3ED4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F66F031-412F-43E3-946B-1034DBD3ED4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F66F031-412F-43E3-946B-1034DBD3ED4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F66F031-412F-43E3-946B-1034DBD3ED4D}.Release|Any CPU.Build.0 = Release|Any CPU - {A1321882-2C76-4105-A0BD-9500D2402A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1321882-2C76-4105-A0BD-9500D2402A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1321882-2C76-4105-A0BD-9500D2402A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1321882-2C76-4105-A0BD-9500D2402A2B}.Release|Any CPU.Build.0 = Release|Any CPU - {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Debug|Any CPU.Build.0 = Debug|Any CPU - {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Release|Any CPU.ActiveCfg = Release|Any CPU - {970C789F-EEF1-08D6-6053-36CF2DCD8E68}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {8EA21069-A6E8-4DAB-8368-796BAA10B40E} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {482379B4-2A21-4152-B84E-554006FCAC95} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {ED5B2080-C706-4150-897D-59482F41BC34} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {EA8020A5-B936-415A-8A1B-0DC8CA7914B1} = {407E264A-43D3-4139-BF62-D505EECA36F8} - {25C0D76C-E7D2-43CC-8D0E-BECB0438527B} = {C6F3C570-C68C-4A95-960E-82778306BDBA} - {7063CEBE-CE42-4E4D-8EE6-EB719CEF2F2F} = {C6F3C570-C68C-4A95-960E-82778306BDBA} - {AEBFD35A-48BF-45ED-8AB4-F9DB1882D12D} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {5659A831-8170-4398-A130-D521844BB4C0} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {6DC66BBC-6B35-4CCC-A48B-6AD28C336C80} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {DD0A6164-CE88-49A0-AF4E-7A56ACBB13BB} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {FC2C7F03-69E0-4CCE-A582-1FB6D8D11560} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {919CDF8C-463A-4E82-AFFD-DF8A6B904600} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {CE437FB2-510F-4DCE-8A1F-AED747DAA4EB} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {5B1AF24E-90FC-4C21-AF9C-090FE32027E3} = {C6F3C570-C68C-4A95-960E-82778306BDBA} - {BDB46997-84D1-4CB5-B967-7F820820CB8E} = {C6F3C570-C68C-4A95-960E-82778306BDBA} - {DA78EB72-93B1-4A77-8525-79AF3EEC4C8D} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {B9E04359-B835-443C-8D04-BBDD03F00A89} = {C6F3C570-C68C-4A95-960E-82778306BDBA} - {9BC9B26A-E73F-46D4-B788-06C3A8AE63EB} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {5F66F031-412F-43E3-946B-1034DBD3ED4D} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - {A1321882-2C76-4105-A0BD-9500D2402A2B} = {C6F3C570-C68C-4A95-960E-82778306BDBA} - {970C789F-EEF1-08D6-6053-36CF2DCD8E68} = {34B53F49-F5C5-4850-B79E-59AD130379C6} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {92916FA2-FCAC-406E-BF3F-0A2CE9512EF0} - EndGlobalSection -EndGlobal diff --git a/ZiggyCreatures.FusionCache.slnx b/ZiggyCreatures.FusionCache.slnx new file mode 100644 index 00000000..312e5be2 --- /dev/null +++ b/ZiggyCreatures.FusionCache.slnx @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 11d5ffa1344837758b166f0da9e61b3d5b84db2d Mon Sep 17 00:00:00 2001 From: Jody Donetti Date: Sat, 18 Oct 2025 23:25:27 +0200 Subject: [PATCH 5/6] Disable TreatWarningsAsErrors for now --- .../ZiggyCreatures.FusionCache.Backplane.NATS.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj b/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj index 96a301a6..609942f2 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/ZiggyCreatures.FusionCache.Backplane.NATS.csproj @@ -8,6 +8,7 @@ ZiggyCreatures.Caching.Fusion.Backplane.NATS true 1.0.0 + false From 67f705029e1efe16d10b9c9c5b13c3468fc18d45 Mon Sep 17 00:00:00 2001 From: Jody Donetti Date: Sat, 18 Oct 2025 23:25:55 +0200 Subject: [PATCH 6/6] Added a couple of comments about things to look out for --- .../NatsBackplane.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs index 6688e9e7..1ad3103f 100644 --- a/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs +++ b/src/ZiggyCreatures.FusionCache.Backplane.NATS/NatsBackplane.cs @@ -1,14 +1,8 @@ -using System.Buffers; -using System.Text.Json; - -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using NATS.Client.Core; -using ZiggyCreatures.Caching.Fusion.Internals; - namespace ZiggyCreatures.Caching.Fusion.Backplane.NATS; /// @@ -82,8 +76,8 @@ public async ValueTask SubscribeAsync(BackplaneSubscriptionOptions subscriptionO { using (msg.Data) { - if(BackplaneMessage.TryParse(msg.Data.Span, out BackplaneMessage message)) - { + if (BackplaneMessage.TryParse(msg.Data.Span, out BackplaneMessage message)) + { await OnMessageAsync(message).ConfigureAwait(false); } } @@ -96,7 +90,8 @@ public async ValueTask SubscribeAsync(BackplaneSubscriptionOptions subscriptionO /// public void Subscribe(BackplaneSubscriptionOptions options) { - SubscribeAsync(options).AsTask().Wait(); + // TODO: IS THERE A BETTER WAY INSTEAD OF SYNC OVER ASYNC ? + SubscribeAsync(options).GetAwaiter().GetResult(); } /// @@ -112,12 +107,14 @@ public async ValueTask UnsubscribeAsync() /// public void Unsubscribe() { - UnsubscribeAsync().AsTask().Wait(); + // TODO: IS THERE A BETTER WAY INSTEAD OF SYNC OVER ASYNC ? + UnsubscribeAsync().GetAwaiter().GetResult(); } /// public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) { + // TH TYPE NatsBufferWriter SEEMS TO BE DISPOSABLE: SHOULD IT BE DISPOSED? var writer = new NatsBufferWriter(); message.WriteTo(writer); await _connection.PublishAsync(_channelName, writer).ConfigureAwait(false); @@ -126,7 +123,8 @@ public async ValueTask PublishAsync(BackplaneMessage message, FusionCacheEntryOp /// public void Publish(in BackplaneMessage message, FusionCacheEntryOptions options, CancellationToken token = default) { - PublishAsync(message, options, token).AsTask().Wait(); + // TODO: IS THERE A BETTER WAY INSTEAD OF SYNC OVER ASYNC ? + PublishAsync(message, options, token).GetAwaiter().GetResult(); } internal async ValueTask OnMessageAsync(BackplaneMessage message)