diff --git a/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/SerializersBenchmark.cs b/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/SerializersBenchmark.cs index 8d74899d..c35196e8 100644 --- a/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/SerializersBenchmark.cs +++ b/benchmarks/ZiggyCreatures.FusionCache.Benchmarks/SerializersBenchmark.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System.Buffers; +using System.Runtime.Serialization; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; @@ -8,6 +9,7 @@ using BenchmarkDotNet.Toolchains.InProcess.Emit; using MemoryPack; using MessagePack; +using ZiggyCreatures.Caching.Fusion.Internals; using ZiggyCreatures.Caching.Fusion.Serialization; using ZiggyCreatures.Caching.Fusion.Serialization.CysharpMemoryPack; using ZiggyCreatures.Caching.Fusion.Serialization.NeueccMessagePack; @@ -71,7 +73,7 @@ public Config() } [ParamsSource(nameof(GetSerializers))] - public IFusionCacheSerializer Serializer = null!; + public IBufferFusionCacheSerializer Serializer = null!; protected List _Models = []; protected byte[] _Blob = null!; @@ -92,25 +94,53 @@ public void Serialize() Serializer.Serialize(_Models); } + [Benchmark] + public void Serialize_Buffer() + { + using var writer = new ArrayPoolBufferWriter(); + Serializer.Serialize(_Models, writer); + } + [Benchmark] public void Deserialize() { Serializer.Deserialize>(_Blob); } + [Benchmark] + public void Deserialize_Buffer() + { + var blob = new ReadOnlySequence(_Blob); + Serializer.Deserialize>(in blob); + } + [Benchmark] public async Task SerializeAsync() { await Serializer.SerializeAsync(_Models).ConfigureAwait(false); } + [Benchmark] + public async Task SerializeAsync_Buffer() + { + using var writer = new ArrayPoolBufferWriter(); + await Serializer.SerializeAsync(_Models, writer).ConfigureAwait(false); + } + [Benchmark] public async Task DeserializeAsync() { await Serializer.DeserializeAsync>(_Blob).ConfigureAwait(false); } - public static IEnumerable GetSerializers() + [Benchmark] + public async Task DeserializeAsync_Buffer() + { + var blob = new ReadOnlySequence(_Blob); + await Serializer.DeserializeAsync>(blob).ConfigureAwait(false); + } + + public static IEnumerable GetSerializers() { yield return new FusionCacheCysharpMemoryPackSerializer(); yield return new FusionCacheNeueccMessagePackSerializer(); diff --git a/src/ZiggyCreatures.FusionCache.Chaos/ChaosBufferDistributedCache.cs b/src/ZiggyCreatures.FusionCache.Chaos/ChaosBufferDistributedCache.cs new file mode 100644 index 00000000..47010407 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache.Chaos/ChaosBufferDistributedCache.cs @@ -0,0 +1,52 @@ +using System.Buffers; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; + +namespace ZiggyCreatures.Caching.Fusion.Chaos; + +/// +/// An implementation of that acts on behalf of another one, but with a (controllable) amount of chaos in-between. +/// +public class ChaosBufferDistributedCache : ChaosDistributedCache, IBufferDistributedCache +{ + private readonly IBufferDistributedCache _innerCache; + + /// + /// Initializes a new instance of the ChaosDistributedCache class. + /// + /// The actual used if and when chaos does not happen. + /// The logger to use, or . + public ChaosBufferDistributedCache(IBufferDistributedCache innerCache, ILogger? logger = null) + : base(innerCache, logger) + { + _innerCache = innerCache; + } + + /// + public bool TryGet(string key, IBufferWriter destination) + { + MaybeChaos(); + return _innerCache.TryGet(key, destination); + } + + /// + public async ValueTask TryGetAsync(string key, IBufferWriter destination, CancellationToken token = default) + { + await MaybeChaosAsync(token).ConfigureAwait(false); + return await _innerCache.TryGetAsync(key, destination, token).ConfigureAwait(false); + } + + /// + public void Set(string key, ReadOnlySequence value, DistributedCacheEntryOptions options) + { + MaybeChaos(); + _innerCache.Set(key, value, options); + } + + /// + public async ValueTask SetAsync(string key, ReadOnlySequence value, DistributedCacheEntryOptions options, CancellationToken token = default) + { + await MaybeChaosAsync(token).ConfigureAwait(false); + await _innerCache.SetAsync(key, value, options, token).ConfigureAwait(false); + } +} diff --git a/src/ZiggyCreatures.FusionCache.Chaos/ChaosBufferSerializer.cs b/src/ZiggyCreatures.FusionCache.Chaos/ChaosBufferSerializer.cs new file mode 100644 index 00000000..d91a6d8f --- /dev/null +++ b/src/ZiggyCreatures.FusionCache.Chaos/ChaosBufferSerializer.cs @@ -0,0 +1,52 @@ +using System.Buffers; +using Microsoft.Extensions.Logging; +using ZiggyCreatures.Caching.Fusion.Serialization; + +namespace ZiggyCreatures.Caching.Fusion.Chaos; + +/// +/// An implementation of that acts on behalf of another one, but with a (controllable) amount of chaos in-between. +/// +public class ChaosBufferSerializer : ChaosSerializer, IBufferFusionCacheSerializer +{ + private readonly IBufferFusionCacheSerializer _innerSerializer; + + /// + /// Initializes a new instance of the ChaosSerializer class. + /// + /// The actual used if and when chaos does not happen. + /// The logger to use, or . + public ChaosBufferSerializer(IBufferFusionCacheSerializer innerSerializer, ILogger? logger = null) + : base(innerSerializer, logger) + { + _innerSerializer = innerSerializer; + } + + /// + public void Serialize(T? obj, IBufferWriter destination) + { + MaybeChaos(); + _innerSerializer.Serialize(obj, destination); + } + + /// + public T? Deserialize(in ReadOnlySequence data) + { + MaybeChaos(); + return _innerSerializer.Deserialize(data); + } + + /// + public async ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default) + { + await MaybeChaosAsync().ConfigureAwait(false); + await _innerSerializer.SerializeAsync(obj, destination, token).ConfigureAwait(false); + } + + /// + public async ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default) + { + await MaybeChaosAsync().ConfigureAwait(false); + return await _innerSerializer.DeserializeAsync(data, token).ConfigureAwait(false); + } +} diff --git a/src/ZiggyCreatures.FusionCache.Chaos/ChaosDistributedCache.cs b/src/ZiggyCreatures.FusionCache.Chaos/ChaosDistributedCache.cs index 94633d39..a058342b 100644 --- a/src/ZiggyCreatures.FusionCache.Chaos/ChaosDistributedCache.cs +++ b/src/ZiggyCreatures.FusionCache.Chaos/ChaosDistributedCache.cs @@ -24,6 +24,18 @@ public ChaosDistributedCache(IDistributedCache innerCache, ILogger + /// Initializes a new instance of the ChaosDistributedCache class that implements the interface if the given does. + /// + /// The actual used if and when chaos does not happen. + /// The logger to use, or . + public static ChaosDistributedCache Create(IDistributedCache innerCache, ILogger? logger = null) + { + return innerCache is IBufferDistributedCache bufferCache + ? new ChaosBufferDistributedCache(bufferCache, logger) + : new ChaosDistributedCache(innerCache, logger); + } + /// public byte[]? Get(string key) { diff --git a/src/ZiggyCreatures.FusionCache.Chaos/ChaosSerializer.cs b/src/ZiggyCreatures.FusionCache.Chaos/ChaosSerializer.cs index 2d86fd10..aaf4ce31 100644 --- a/src/ZiggyCreatures.FusionCache.Chaos/ChaosSerializer.cs +++ b/src/ZiggyCreatures.FusionCache.Chaos/ChaosSerializer.cs @@ -24,6 +24,18 @@ public ChaosSerializer(IFusionCacheSerializer innerSerializer, ILogger + /// Initializes a new instance of the ChaosSerializer class that implements the interface if the given does. + /// + /// The actual used if and when chaos does not happen. + /// The logger to use, or . + public static ChaosSerializer Create(IFusionCacheSerializer innerSerializer, ILogger? logger = null) + { + return innerSerializer is IBufferFusionCacheSerializer bufferSerializer + ? new ChaosBufferSerializer(bufferSerializer, logger) + : new ChaosSerializer(innerSerializer, logger); + } + /// public byte[] Serialize(T? obj) { diff --git a/src/ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack/FusionCacheCysharpMemoryPackSerializer.cs b/src/ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack/FusionCacheCysharpMemoryPackSerializer.cs index 265ed3bc..6a7bd2cd 100644 --- a/src/ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack/FusionCacheCysharpMemoryPackSerializer.cs +++ b/src/ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack/FusionCacheCysharpMemoryPackSerializer.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Buffers; +using System.Runtime.CompilerServices; using MemoryPack; using ZiggyCreatures.Caching.Fusion.Internals; using ZiggyCreatures.Caching.Fusion.Internals.Distributed; @@ -10,7 +11,7 @@ namespace ZiggyCreatures.Caching.Fusion.Serialization.CysharpMemoryPack; /// An implementation of which uses Cysharp's MemoryPack serializer. /// public class FusionCacheCysharpMemoryPackSerializer - : IFusionCacheSerializer + : IFusionCacheSerializer, IBufferFusionCacheSerializer { /// /// The options class for the class. @@ -55,7 +56,8 @@ public FusionCacheCysharpMemoryPackSerializer(Options? options) public byte[] Serialize(T? obj) { var buffer = new ArrayPoolBufferWriter(); - MemoryPackWriter writer = new(ref buffer, MemoryPackWriterOptionalStatePool.Rent(_serializerOptions)); + using var writerState = MemoryPackWriterOptionalStatePool.Rent(_serializerOptions); + MemoryPackWriter writer = new(ref buffer, writerState); try { MemoryPackSerializer.Serialize(ref writer, obj); @@ -67,6 +69,13 @@ public byte[] Serialize(T? obj) } } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Serialize(T? obj, IBufferWriter destination) + { + MemoryPackSerializer.Serialize(destination, obj, _serializerOptions); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T? Deserialize(byte[] data) @@ -74,6 +83,13 @@ public byte[] Serialize(T? obj) return MemoryPackSerializer.Deserialize(data, _serializerOptions); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T? Deserialize(in ReadOnlySequence data) + { + return MemoryPackSerializer.Deserialize(in data, _serializerOptions); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask SerializeAsync(T? obj, CancellationToken token = default) @@ -81,6 +97,14 @@ public ValueTask SerializeAsync(T? obj, CancellationToken token = def return new ValueTask(Serialize(obj)); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default) + { + Serialize(obj, destination); + return new ValueTask(); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask DeserializeAsync(byte[] data, CancellationToken token = default) @@ -88,6 +112,13 @@ public ValueTask SerializeAsync(T? obj, CancellationToken token = def return new ValueTask(Deserialize(data)); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default) + { + return new ValueTask(Deserialize(in data)); + } + /// public override string ToString() => $"{GetType().Name}"; } diff --git a/src/ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack/FusionCacheNeueccMessagePackSerializer.cs b/src/ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack/FusionCacheNeueccMessagePackSerializer.cs index 10e464fd..3a71cbbf 100644 --- a/src/ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack/FusionCacheNeueccMessagePackSerializer.cs +++ b/src/ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack/FusionCacheNeueccMessagePackSerializer.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Buffers; +using System.Runtime.CompilerServices; using MessagePack; using MessagePack.Resolvers; @@ -10,7 +11,7 @@ namespace ZiggyCreatures.Caching.Fusion.Serialization.NeueccMessagePack; /// An implementation of which uses Neuecc's famous MessagePack serializer. /// public class FusionCacheNeueccMessagePackSerializer - : IFusionCacheSerializer + : IFusionCacheSerializer, IBufferFusionCacheSerializer { /// /// The options class for the class. @@ -54,6 +55,13 @@ public byte[] Serialize(T? obj) return writer.ToArray(); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Serialize(T? obj, IBufferWriter destination) + { + MessagePackSerializer.Serialize(destination, obj, _serializerOptions); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T? Deserialize(byte[] data) @@ -61,6 +69,13 @@ public byte[] Serialize(T? obj) return MessagePackSerializer.Deserialize(data.AsMemory(), _serializerOptions); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T? Deserialize(in ReadOnlySequence data) + { + return MessagePackSerializer.Deserialize(data, _serializerOptions); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask SerializeAsync(T? obj, CancellationToken token = default) @@ -69,6 +84,15 @@ public ValueTask SerializeAsync(T? obj, CancellationToken token = def return new ValueTask(Serialize(obj)); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default) + { + // PER @neuecc 'S SUGGESTION: AVOID AWAITING ON A MEMORY STREAM + Serialize(obj, destination); + return new ValueTask(); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask DeserializeAsync(byte[] data, CancellationToken token = default) @@ -77,6 +101,14 @@ public ValueTask SerializeAsync(T? obj, CancellationToken token = def return new ValueTask(Deserialize(data)); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default) + { + // PER @neuecc 'S SUGGESTION: AVOID AWAITING ON A MEMORY STREAM + return new ValueTask(Deserialize(in data)); + } + /// public override string ToString() => $"{GetType().Name}"; } diff --git a/src/ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson/FusionCacheNewtonsoftJsonSerializer.cs b/src/ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson/FusionCacheNewtonsoftJsonSerializer.cs index 9f06e0c3..84f7ca96 100644 --- a/src/ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson/FusionCacheNewtonsoftJsonSerializer.cs +++ b/src/ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson/FusionCacheNewtonsoftJsonSerializer.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Runtime.InteropServices; using System.Text; using Newtonsoft.Json; @@ -17,7 +18,7 @@ internal class JsonArrayPool : IArrayPool /// An implementation of which uses the Newtonsoft Json.NET serializer. /// public class FusionCacheNewtonsoftJsonSerializer - : IFusionCacheSerializer + : IFusionCacheSerializer, IBufferFusionCacheSerializer { private readonly JsonSerializer _jsonSerializer; /// @@ -58,13 +59,29 @@ public FusionCacheNewtonsoftJsonSerializer(Options? options) /// public byte[] Serialize(T? obj) { - using var stream = new ArrayPoolWritableStream(); + using var bufferWriter = new ArrayPoolBufferWriter(); + + using (var stream = new BufferWriterStream(bufferWriter)) + { + using var writer = new StreamWriter(stream); + using JsonTextWriter jsonWriter = new JsonTextWriter(writer); + jsonWriter.ArrayPool = JsonArrayPool.Shared; + _jsonSerializer.Serialize(jsonWriter, obj); + jsonWriter.Flush(); + } + + return bufferWriter.ToArray(); + } + + /// + public void Serialize(T? obj, IBufferWriter destination) + { + using var stream = new BufferWriterStream(destination); using var writer = new StreamWriter(stream); using JsonTextWriter jsonWriter = new JsonTextWriter(writer); jsonWriter.ArrayPool = JsonArrayPool.Shared; _jsonSerializer.Serialize(jsonWriter, obj); jsonWriter.Flush(); - return stream.GetBytes(); } /// @@ -77,18 +94,41 @@ public byte[] Serialize(T? obj) return _jsonSerializer.Deserialize(jsonReader); } + /// + public T? Deserialize(in ReadOnlySequence data) + { + using var stream = new ReadOnlySequenceStream(in data); + using var reader = new StreamReader(stream, _encoding); + using var jsonReader = new JsonTextReader(reader); + jsonReader.ArrayPool = JsonArrayPool.Shared; + return _jsonSerializer.Deserialize(jsonReader); + } + /// public ValueTask SerializeAsync(T? obj, CancellationToken token = default) { return new ValueTask(Serialize(obj)); } + /// + public ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default) + { + Serialize(obj, destination); + return new ValueTask(); + } + /// public ValueTask DeserializeAsync(byte[] data, CancellationToken token = default) { return new ValueTask(Deserialize(data)); } + /// + public ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default) + { + return new ValueTask(Deserialize(in data)); + } + /// public override string ToString() => $"{GetType().Name}"; } diff --git a/src/ZiggyCreatures.FusionCache.Serialization.ProtoBufNet/FusionCacheProtoBufNetSerializer.cs b/src/ZiggyCreatures.FusionCache.Serialization.ProtoBufNet/FusionCacheProtoBufNetSerializer.cs index 0b5322bb..79737998 100644 --- a/src/ZiggyCreatures.FusionCache.Serialization.ProtoBufNet/FusionCacheProtoBufNetSerializer.cs +++ b/src/ZiggyCreatures.FusionCache.Serialization.ProtoBufNet/FusionCacheProtoBufNetSerializer.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System.Buffers; +using System.Collections.Concurrent; using System.Runtime.CompilerServices; using ProtoBuf.Meta; using ZiggyCreatures.Caching.Fusion.Internals; @@ -11,7 +12,7 @@ namespace ZiggyCreatures.Caching.Fusion.Serialization.ProtoBufNet; /// An implementation of which uses protobuf-net, one of the most used .NET Protobuf serializer, by Marc Gravell. /// public class FusionCacheProtoBufNetSerializer - : IFusionCacheSerializer + : IFusionCacheSerializer, IBufferFusionCacheSerializer { /// /// The options class for the class. @@ -102,9 +103,17 @@ private void MaybeRegisterDistributedEntryModel() public byte[] Serialize(T? obj) { MaybeRegisterDistributedEntryModel(); - using var stream = new ArrayPoolWritableStream(); - _model.Serialize(stream, obj); - return stream.GetBytes(); + using var writer = new ArrayPoolBufferWriter(); + _model.Serialize(writer, obj); + return writer.ToArray(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Serialize(T? obj, IBufferWriter destination) + { + MaybeRegisterDistributedEntryModel(); + _model.Serialize(destination, obj); } /// @@ -118,18 +127,44 @@ public byte[] Serialize(T? obj) return _model.Deserialize((ReadOnlySpan)data); } + /// + public T? Deserialize(in ReadOnlySequence data) + { + if (data.Length == 0) + return default; + + MaybeRegisterDistributedEntryModel(); + + return _model.Deserialize(data); + } + /// public ValueTask SerializeAsync(T? obj, CancellationToken token = default) { return new ValueTask(Serialize(obj)); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default) + { + Serialize(obj, destination); + return new ValueTask(); + } + /// public ValueTask DeserializeAsync(byte[] data, CancellationToken token = default) { return new ValueTask(Deserialize(data)); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default) + { + return new ValueTask(Deserialize(in data)); + } + /// public override string ToString() => GetType().Name; } diff --git a/src/ZiggyCreatures.FusionCache.Serialization.ServiceStackJson/FusionCacheServiceStackJsonSerializer.cs b/src/ZiggyCreatures.FusionCache.Serialization.ServiceStackJson/FusionCacheServiceStackJsonSerializer.cs index 3bdceef3..6d8fff87 100644 --- a/src/ZiggyCreatures.FusionCache.Serialization.ServiceStackJson/FusionCacheServiceStackJsonSerializer.cs +++ b/src/ZiggyCreatures.FusionCache.Serialization.ServiceStackJson/FusionCacheServiceStackJsonSerializer.cs @@ -1,7 +1,7 @@ using System.Buffers; +using System.Runtime.InteropServices; using System.Text; using ServiceStack.Text; - using ZiggyCreatures.Caching.Fusion.Internals; namespace ZiggyCreatures.Caching.Fusion.Serialization.ServiceStackJson; @@ -10,14 +10,11 @@ namespace ZiggyCreatures.Caching.Fusion.Serialization.ServiceStackJson; /// An implementation of which uses the ServiceStack JSON serializer. /// public class FusionCacheServiceStackJsonSerializer - : IFusionCacheSerializer + : IFusionCacheSerializer, IBufferFusionCacheSerializer { static FusionCacheServiceStackJsonSerializer() { - JsConfig.Init(new Config - { - DateHandler = DateHandler.ISO8601 - }); + JsConfig.Init(new Config { DateHandler = DateHandler.ISO8601 }); } /// @@ -30,9 +27,21 @@ public FusionCacheServiceStackJsonSerializer() /// public byte[] Serialize(T? obj) { - using var stream = new ArrayPoolWritableStream(); + using var bufferWriter = new ArrayPoolBufferWriter(); + + using (var stream = new BufferWriterStream(bufferWriter)) + { + JsonSerializer.SerializeToStream(obj, stream); + } + + return bufferWriter.ToArray(); + } + + /// + public void Serialize(T? obj, IBufferWriter destination) + { + using var stream = new BufferWriterStream(destination); JsonSerializer.SerializeToStream(obj, stream); - return stream.GetBytes(); } /// @@ -42,8 +51,30 @@ public byte[] Serialize(T? obj) var chars = ArrayPool.Shared.Rent(numChars); try { - Encoding.UTF8.GetChars(data, 0, data.Length, chars, 0); - return JsonSerializer.DeserializeFromSpan(chars.AsSpan(0, numChars)); + var written = Encoding.UTF8.GetChars(data, 0, data.Length, chars, 0); + return JsonSerializer.DeserializeFromSpan(chars.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(chars); + } + } + + /// + public T? Deserialize(in ReadOnlySequence data) + { + if (!data.IsSingleSegment || !MemoryMarshal.TryGetArray(data.First, out var segment)) + { + var bytes = data.ToArray(); + segment = new ArraySegment(bytes); + } + + int numChars = Encoding.UTF8.GetCharCount(segment.Array!, segment.Offset, segment.Count); + var chars = ArrayPool.Shared.Rent(numChars); + try + { + var written = Encoding.UTF8.GetChars(segment.Array!, segment.Offset, segment.Count, chars, 0); + return JsonSerializer.DeserializeFromSpan(chars.AsSpan(0, written)); } finally { @@ -62,6 +93,13 @@ public ValueTask SerializeAsync(T? obj, CancellationToken token = def //return stream.ToArray(); } + /// + public ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default) + { + Serialize(obj, destination); + return new ValueTask(); + } + /// public ValueTask DeserializeAsync(byte[] data, CancellationToken token = default) { @@ -72,6 +110,12 @@ public ValueTask SerializeAsync(T? obj, CancellationToken token = def //return await JsonSerializer.DeserializeFromStreamAsync(stream); } + /// + public ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default) + { + return new ValueTask(Deserialize(in data)); + } + /// public override string ToString() => GetType().Name; } diff --git a/src/ZiggyCreatures.FusionCache.Serialization.SystemTextJson/FusionCacheSystemTextJsonSerializer.cs b/src/ZiggyCreatures.FusionCache.Serialization.SystemTextJson/FusionCacheSystemTextJsonSerializer.cs index 865235e6..5bfefec7 100644 --- a/src/ZiggyCreatures.FusionCache.Serialization.SystemTextJson/FusionCacheSystemTextJsonSerializer.cs +++ b/src/ZiggyCreatures.FusionCache.Serialization.SystemTextJson/FusionCacheSystemTextJsonSerializer.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Buffers; +using System.Text.Json; namespace ZiggyCreatures.Caching.Fusion.Serialization.SystemTextJson; @@ -6,7 +7,7 @@ namespace ZiggyCreatures.Caching.Fusion.Serialization.SystemTextJson; /// An implementation of which uses the System.Text.Json serializer. /// public class FusionCacheSystemTextJsonSerializer - : IFusionCacheSerializer + : IFusionCacheSerializer, IBufferFusionCacheSerializer { /// /// The options class for the class. @@ -46,24 +47,75 @@ public byte[] Serialize(T? obj) return JsonSerializer.SerializeToUtf8Bytes(obj, _options?.SerializerOptions); } + /// + public void Serialize(T? obj, IBufferWriter destination) + { + var options = _options?.SerializerOptions ?? JsonSerializerOptions.Default; + var writerOptions = new JsonWriterOptions + { + Encoder = options.Encoder, + Indented = options.WriteIndented, + MaxDepth = options.MaxDepth, + SkipValidation = true, + }; + + using var writer = new Utf8JsonWriter(destination, writerOptions); + + JsonSerializer.Serialize(writer, obj, options); + } + /// public T? Deserialize(byte[] data) { return JsonSerializer.Deserialize(data, _options?.SerializerOptions); } + /// + public T? Deserialize(in ReadOnlySequence data) + { + var options = _options?.SerializerOptions ?? JsonSerializerOptions.Default; + + if (data.IsSingleSegment) + { + return JsonSerializer.Deserialize(data.First.Span, options); + } + + var readerOptions = new JsonReaderOptions + { + AllowTrailingCommas = options.AllowTrailingCommas, + CommentHandling = options.ReadCommentHandling, + MaxDepth = options.MaxDepth, + }; + + var reader = new Utf8JsonReader(data, readerOptions); + return JsonSerializer.Deserialize(ref reader, options); + } + /// public ValueTask SerializeAsync(T? obj, CancellationToken token = default) { return new ValueTask(Serialize(obj)); } + /// + public ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default) + { + Serialize(obj, destination); + return new ValueTask(); + } + /// public ValueTask DeserializeAsync(byte[] data, CancellationToken token = default) { return new ValueTask(Deserialize(data)); } + /// + public ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default) + { + return new ValueTask(Deserialize(in data)); + } + /// public override string ToString() => GetType().Name; } diff --git a/src/ZiggyCreatures.FusionCache/FusionCache.cs b/src/ZiggyCreatures.FusionCache/FusionCache.cs index 193d13f7..2a580338 100644 --- a/src/ZiggyCreatures.FusionCache/FusionCache.cs +++ b/src/ZiggyCreatures.FusionCache/FusionCache.cs @@ -44,7 +44,7 @@ public sealed partial class FusionCache private readonly bool _mcaCanClear; // DISTRIBUTED CACHE - private DistributedCacheAccessor? _dca; + private IDistributedCacheAccessor? _dca; private IFusionCacheSerializer? _serializer; // BACKPLANE @@ -336,7 +336,7 @@ internal MemoryCacheAccessor MemoryCacheAccessor // DISTRIBUTED ACCESSOR - internal DistributedCacheAccessor? DistributedCacheAccessor + internal IDistributedCacheAccessor? DistributedCacheAccessor { get { return _dca; } } @@ -823,7 +823,9 @@ public IFusionCache SetupDistributedCache(IDistributedCache distributedCache) if (_logger?.IsEnabled(LogLevel.Debug) ?? false) _logger.Log(LogLevel.Debug, "FUSION [N={CacheName} I={CacheInstanceId}]: setup distributed cache (CACHE={DistributedCacheType})", CacheName, InstanceId, distributedCache.GetType().FullName); - _dca = new DistributedCacheAccessor(distributedCache, _serializer, _options, _logger, _events.Distributed); + _dca = distributedCache is IBufferDistributedCache bufferDistributedCache && _serializer is IBufferFusionCacheSerializer bufferSerializer + ? new BufferDistributedCacheAccessor(bufferDistributedCache, bufferSerializer, _options, _logger, _events.Distributed) + : new ClassicDistributedCacheAccessor(distributedCache, _serializer, _options, _logger, _events.Distributed); return this; } diff --git a/src/ZiggyCreatures.FusionCache/FusionCache_Async.cs b/src/ZiggyCreatures.FusionCache/FusionCache_Async.cs index cc7dba30..17b62987 100644 --- a/src/ZiggyCreatures.FusionCache/FusionCache_Async.cs +++ b/src/ZiggyCreatures.FusionCache/FusionCache_Async.cs @@ -1129,7 +1129,7 @@ public async ValueTask ClearAsync(bool allowFailSafe = true, FusionCacheEntryOpt // DISTRIBUTED ACTIONS - private async ValueTask ExecuteDistributedActionAsync(string operationId, string key, FusionCacheAction action, long timestamp, Func> distributedCacheAction, Func> backplaneAction, FusionCacheEntryOptions options, object? distributedLockObj, CancellationToken token) + private async ValueTask ExecuteDistributedActionAsync(string operationId, string key, FusionCacheAction action, long timestamp, Func> distributedCacheAction, Func> backplaneAction, FusionCacheEntryOptions options, object? distributedLockObj, CancellationToken token) { if (RequiresDistributedOperations(options) == false) { diff --git a/src/ZiggyCreatures.FusionCache/FusionCache_Sync.cs b/src/ZiggyCreatures.FusionCache/FusionCache_Sync.cs index 96505860..baa52afc 100644 --- a/src/ZiggyCreatures.FusionCache/FusionCache_Sync.cs +++ b/src/ZiggyCreatures.FusionCache/FusionCache_Sync.cs @@ -1130,7 +1130,7 @@ public void Clear(bool allowFailSafe = true, FusionCacheEntryOptions? options = // DISTRIBUTED ACTIONS - private void ExecuteDistributedAction(string operationId, string key, FusionCacheAction action, long timestamp, Func distributedCacheAction, Func backplaneAction, FusionCacheEntryOptions options, object? distributedLockObj, CancellationToken token) + private void ExecuteDistributedAction(string operationId, string key, FusionCacheAction action, long timestamp, Func distributedCacheAction, Func backplaneAction, FusionCacheEntryOptions options, object? distributedLockObj, CancellationToken token) { if (RequiresDistributedOperations(options) == false) { diff --git a/src/ZiggyCreatures.FusionCache/Internals/ArrayPoolBufferWriter.cs b/src/ZiggyCreatures.FusionCache/Internals/ArrayPoolBufferWriter.cs index 45fa6a60..f42627d1 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/ArrayPoolBufferWriter.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/ArrayPoolBufferWriter.cs @@ -57,9 +57,14 @@ private void Reset() [MethodImpl(MethodImplOptions.AggressiveInlining)] public Memory GetMemory(int sizeHint = 0) { + if (sizeHint <= 0) + { + sizeHint = 1; + } + var requiredCapacity = _bytesWritten + sizeHint; var currentBufferLength = _buffer.Length; - if (requiredCapacity >= currentBufferLength) + if (requiredCapacity > currentBufferLength) { var newSize = Math.Max(currentBufferLength * 2, requiredCapacity); var newBuffer = _arrayPool.Rent(newSize); @@ -73,6 +78,16 @@ public Memory GetMemory(int sizeHint = 0) return _buffer.AsMemory(_bytesWritten); } + /// + /// Returns the data written so far. + /// Any other method call on the writer makes the returned sequence invalid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySequence GetWrittenBuffer() + { + return new ReadOnlySequence(_buffer, 0, _bytesWritten); + } + /// /// Returns the buffer as an array of /// diff --git a/src/ZiggyCreatures.FusionCache/Internals/ArrayPoolWritableStream.cs b/src/ZiggyCreatures.FusionCache/Internals/ArrayPoolWritableStream.cs deleted file mode 100644 index 790b1709..00000000 --- a/src/ZiggyCreatures.FusionCache/Internals/ArrayPoolWritableStream.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace ZiggyCreatures.Caching.Fusion.Internals; - -/// -/// A writable stream that uses an to manage its buffer. -/// -public sealed class ArrayPoolWritableStream : Stream -{ - private readonly ArrayPoolBufferWriter _buffer; - - /// - /// Initializes a new instance of the class. - /// - public ArrayPoolWritableStream() - { - _buffer = new ArrayPoolBufferWriter(); - } - - /// - public override bool CanRead => false; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => true; - - /// - public override long Length => _buffer.BytesWritten; - - /// - /// Gets the written bytes as a byte array. - /// - /// The written bytes as a byte array. - public byte[] GetBytes() => _buffer.ToArray(); - - /// - public override long Position - { - get => Length; - set - { - throw new NotSupportedException("Cannot set the position of a writable stream."); - } - } - - /// - public override void Flush() - { - // no-op - return; - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException("Cannot read from a writable stream."); - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("Cannot seek a writable stream."); - } - - /// - public override void SetLength(long value) - { - throw new NotSupportedException("Cannot set the length of a writable stream."); - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - var memory = _buffer.GetSpan(count); - buffer.AsSpan(offset, count).CopyTo(memory); - _buffer.Advance(count); - } - - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - if (disposing) - { - _buffer.Dispose(); - } - } -} diff --git a/src/ZiggyCreatures.FusionCache/Internals/BufferWriterStream.cs b/src/ZiggyCreatures.FusionCache/Internals/BufferWriterStream.cs new file mode 100644 index 00000000..f58760f9 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Internals/BufferWriterStream.cs @@ -0,0 +1,112 @@ +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace ZiggyCreatures.Caching.Fusion.Internals; + +/// +/// A writable stream that proxies the data to the provided . +/// +public sealed class BufferWriterStream : Stream +{ + private readonly IBufferWriter _writer; + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + public BufferWriterStream(IBufferWriter writer) + { + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + } + + /// + public override bool CanRead => false; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => !_disposed; + + /// + public override long Length => throw new NotSupportedException("Cannot get the length of a writable stream."); + + /// + public override long Position + { + get => throw new NotSupportedException("Cannot get the position of a writable stream."); + set => throw new NotSupportedException("Cannot set the position of a writable stream."); + } + + /// + public override void Flush() + { + ThrowIfDisposed(); + // no-op + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Cannot read from a writable stream."); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Cannot seek a writable stream."); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException("Cannot set the length of a writable stream."); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + Write(buffer.AsSpan(offset, count)); + } + + /// + public override void WriteByte(byte value) + { + Write([value]); + } + +#if !NETSTANDARD2_0 + /// + public override void Write(ReadOnlySpan buffer) +#else + private void Write(ReadOnlySpan buffer) +#endif + { + ThrowIfDisposed(); + + var memory = _writer.GetSpan(buffer.Length); + buffer.CopyTo(memory); + _writer.Advance(buffer.Length); + } + + /// + protected override void Dispose(bool disposing) + { + _disposed = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ThrowIfDisposed() + { +#if NETSTANDARD2_0 + if (_disposed) ThrowObjectDisposedSlow(); + + static void ThrowObjectDisposedSlow() + { + throw new ObjectDisposedException(nameof(ReadOnlySequenceStream)); + } +#else + ObjectDisposedException.ThrowIf(_disposed, typeof(ReadOnlySequenceStream)); +#endif + } +} diff --git a/src/ZiggyCreatures.FusionCache/Internals/Distributed/BufferDistributedCacheAccessor.cs b/src/ZiggyCreatures.FusionCache/Internals/Distributed/BufferDistributedCacheAccessor.cs new file mode 100644 index 00000000..7bf17243 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Internals/Distributed/BufferDistributedCacheAccessor.cs @@ -0,0 +1,110 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using ZiggyCreatures.Caching.Fusion.Events; +using ZiggyCreatures.Caching.Fusion.Serialization; + +namespace ZiggyCreatures.Caching.Fusion.Internals.Distributed; + +internal sealed class BufferDistributedCacheAccessor(IBufferDistributedCache distributedCache, IBufferFusionCacheSerializer serializer, FusionCacheOptions options, ILogger? logger, FusionCacheDistributedEventsHub events) + : DistributedCacheAccessor(options, logger, events) +{ + private readonly IBufferDistributedCache _cache = distributedCache ?? throw new ArgumentNullException(nameof(distributedCache)); + private readonly IBufferFusionCacheSerializer _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + + public override IDistributedCache DistributedCache => _cache; + public override IFusionCacheSerializer Serializer => _serializer; + + protected override ArrayPoolBufferWriter Serialize(FusionCacheDistributedEntry obj) + { + var writer = new ArrayPoolBufferWriter(); + _serializer.Serialize(obj, writer); + return writer; + } + + protected override async ValueTask SerializeAsync(FusionCacheDistributedEntry obj, CancellationToken ct) + { + var writer = new ArrayPoolBufferWriter(); + await _serializer.SerializeAsync(obj, writer, ct).ConfigureAwait(false); + return writer; + } + + protected override T? Deserialize(ArrayPoolBufferWriter data) where T : default + { + var buffer = data.GetWrittenBuffer(); + var deserialized = _serializer.Deserialize(in buffer); + + // Release the buffer ONLY if the call was successful. In case of exception, the buffer may still be used. + data.Dispose(); + + return deserialized; + } + + protected override async ValueTask DeserializeAsync(ArrayPoolBufferWriter data, CancellationToken ct) where T : default + { + var buffer = data.GetWrittenBuffer(); + var deserialized = await _serializer.DeserializeAsync(buffer, ct).ConfigureAwait(false); + + // Release the buffer ONLY if the call was successful. In case of exception, the buffer may still be used. + data.Dispose(); + + return deserialized; + } + + protected override ArrayPoolBufferWriter? GetCacheEntry(string key) + { + var writer = new ArrayPoolBufferWriter(); + + if (_cache.TryGet(key, writer)) + { + return writer; + } + + // Release the buffer ONLY if the call was successful. In case of exception, the buffer may still be used. + writer.Dispose(); + return null; + } + + protected override async Task GetCacheEntryAsync(string key, CancellationToken ct) + { + var writer = new ArrayPoolBufferWriter(); + + if (await _cache.TryGetAsync(key, writer, ct).ConfigureAwait(false)) + { + return writer; + } + + // Release the buffer ONLY if the call was successful. In case of exception, the buffer may still be used. + writer.Dispose(); + return null; + } + + protected override void SetCacheEntry(string key, ArrayPoolBufferWriter data, DistributedCacheEntryOptions distributedOptions) + { + var buffer = data.GetWrittenBuffer(); + + _cache.Set(key, buffer, distributedOptions); + + // Release the buffer ONLY if the call was successful. In case of exception, the buffer may still be used. + data.Dispose(); + } + + protected override async Task SetCacheEntryAsync(string key, ArrayPoolBufferWriter data, DistributedCacheEntryOptions distributedOptions, CancellationToken ct) + { + var buffer = data.GetWrittenBuffer(); + + await _cache.SetAsync(key, buffer, distributedOptions, ct).ConfigureAwait(false); + + // Release the buffer ONLY if the call was successful. In case of exception, the buffer may still be used. + data.Dispose(); + } + + protected override void RemoveCacheEntry(string key) + { + _cache.Remove(key); + } + + protected override Task RemoveCacheEntryAsync(string key, CancellationToken ct) + { + return _cache.RemoveAsync(key, ct); + } +} diff --git a/src/ZiggyCreatures.FusionCache/Internals/Distributed/ClassicDistributedCacheAccessor.cs b/src/ZiggyCreatures.FusionCache/Internals/Distributed/ClassicDistributedCacheAccessor.cs new file mode 100644 index 00000000..0dc47ce5 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Internals/Distributed/ClassicDistributedCacheAccessor.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using ZiggyCreatures.Caching.Fusion.Events; +using ZiggyCreatures.Caching.Fusion.Serialization; + +namespace ZiggyCreatures.Caching.Fusion.Internals.Distributed; + +internal sealed class ClassicDistributedCacheAccessor(IDistributedCache distributedCache, IFusionCacheSerializer serializer, FusionCacheOptions options, ILogger? logger, FusionCacheDistributedEventsHub events) + : DistributedCacheAccessor(options, logger, events) +{ + private readonly IDistributedCache _cache = distributedCache ?? throw new ArgumentNullException(nameof(distributedCache)); + private readonly IFusionCacheSerializer _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + + public override IDistributedCache DistributedCache => _cache; + public override IFusionCacheSerializer Serializer => _serializer; + + protected override byte[]? Serialize(FusionCacheDistributedEntry obj) + { + return _serializer.Serialize(obj); + } + + protected override ValueTask SerializeAsync(FusionCacheDistributedEntry obj, CancellationToken ct) + { + return _serializer.SerializeAsync(obj, ct)!; + } + + protected override T? Deserialize(byte[] data) where T : default + { + return _serializer.Deserialize(data); + } + + protected override ValueTask DeserializeAsync(byte[] data, CancellationToken ct) where T : default + { + return _serializer.DeserializeAsync(data, ct); + } + + protected override byte[]? GetCacheEntry(string key) + { + return _cache.Get(key); + } + + protected override Task GetCacheEntryAsync(string key, CancellationToken ct) + { + return _cache.GetAsync(key, ct); + } + + protected override void SetCacheEntry(string key, byte[] data, DistributedCacheEntryOptions distributedOptions) + { + _cache.Set(key, data, distributedOptions); + } + + protected override Task SetCacheEntryAsync(string key, byte[] data, DistributedCacheEntryOptions distributedOptions, CancellationToken ct) + { + return _cache.SetAsync(key, data, distributedOptions, ct); + } + + protected override void RemoveCacheEntry(string key) + { + _cache.Remove(key); + } + + protected override Task RemoveCacheEntryAsync(string key, CancellationToken ct) + { + return _cache.RemoveAsync(key, ct); + } +} diff --git a/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor.cs b/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor.cs index 2f134949..cc9dd61d 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor.cs @@ -6,27 +6,17 @@ namespace ZiggyCreatures.Caching.Fusion.Internals.Distributed; -internal sealed partial class DistributedCacheAccessor +internal abstract partial class DistributedCacheAccessor : IDistributedCacheAccessor + where TSerialized : notnull { - private readonly IDistributedCache _cache; - private readonly IFusionCacheSerializer _serializer; private readonly FusionCacheOptions _options; private readonly ILogger? _logger; private readonly FusionCacheDistributedEventsHub _events; private readonly SimpleCircuitBreaker _breaker; private readonly string _wireFormatToken; - public DistributedCacheAccessor(IDistributedCache distributedCache, IFusionCacheSerializer serializer, FusionCacheOptions options, ILogger? logger, FusionCacheDistributedEventsHub events) + protected DistributedCacheAccessor(FusionCacheOptions options, ILogger? logger, FusionCacheDistributedEventsHub events) { - if (distributedCache is null) - throw new ArgumentNullException(nameof(distributedCache)); - - if (serializer is null) - throw new ArgumentNullException(nameof(serializer)); - - _cache = distributedCache; - _serializer = serializer; - _options = options; _logger = logger; @@ -45,10 +35,9 @@ public DistributedCacheAccessor(IDistributedCache distributedCache, IFusionCache }; } - public IDistributedCache DistributedCache - { - get { return _cache; } - } + public abstract IDistributedCache DistributedCache { get; } + + public abstract IFusionCacheSerializer Serializer { get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private string MaybeProcessCacheKey(string key) @@ -64,7 +53,7 @@ private string MaybeProcessCacheKey(string key) private void UpdateLastError(string operationId, string key) { // NO DISTRIBUTEC CACHE - if (_cache is null) + if (DistributedCache is null) return; var res = _breaker.TryOpen(out var hasChanged); diff --git a/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Async.cs b/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Async.cs index d84d40e1..cfae715e 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Async.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Async.cs @@ -1,11 +1,22 @@ using System.Diagnostics; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using ZiggyCreatures.Caching.Fusion.Internals.Diagnostics; namespace ZiggyCreatures.Caching.Fusion.Internals.Distributed; -internal partial class DistributedCacheAccessor +internal abstract partial class DistributedCacheAccessor { + protected abstract ValueTask SerializeAsync(FusionCacheDistributedEntry obj, CancellationToken ct); + + protected abstract ValueTask DeserializeAsync(TSerialized data, CancellationToken ct); + + protected abstract Task GetCacheEntryAsync(string key, CancellationToken ct); + + protected abstract Task SetCacheEntryAsync(string key, TSerialized data, DistributedCacheEntryOptions distributedOptions, CancellationToken ct); + + protected abstract Task RemoveCacheEntryAsync(string key, CancellationToken ct); + private async ValueTask ExecuteOperationAsync(string operationId, string key, Func action, string actionDescription, FusionCacheEntryOptions options, CancellationToken token) { //if (IsCurrentlyUsable(operationId, key) == false) @@ -70,7 +81,7 @@ public async ValueTask SetEntryAsync(string operationId, string ke var distributedEntry = entry.AsDistributedEntry(options); // SERIALIZATION - byte[]? data; + TSerialized? data; try { if (_logger?.IsEnabled(LogLevel.Trace) ?? false) @@ -78,11 +89,11 @@ public async ValueTask SetEntryAsync(string operationId, string ke if (_options.PreferSyncSerialization) { - data = _serializer.Serialize(distributedEntry); + data = Serialize(distributedEntry); } else { - data = await _serializer.SerializeAsync(distributedEntry, token).ConfigureAwait(false); + data = await SerializeAsync(distributedEntry, token).ConfigureAwait(false); } } catch (Exception exc) @@ -128,7 +139,7 @@ public async ValueTask SetEntryAsync(string operationId, string ke { var distributedOptions = options.ToDistributedCacheEntryOptions(_options, _logger, operationId, key); - await _cache.SetAsync(MaybeProcessCacheKey(key), data, distributedOptions, ct).ConfigureAwait(false); + await SetCacheEntryAsync(MaybeProcessCacheKey(key), data, distributedOptions, ct).ConfigureAwait(false); // EVENT _events.OnSet(operationId, key); @@ -154,12 +165,12 @@ public async ValueTask SetEntryAsync(string operationId, string ke using var activity = Activities.SourceDistributedLevel.StartActivityWithCommonTags(Activities.Names.DistributedGet, _options.CacheName, _options.InstanceId!, key, operationId, CacheLevelKind.Distributed); // GET FROM DISTRIBUTED CACHE - byte[]? data; + TSerialized? data; try { timeout ??= options.GetAppropriateDistributedCacheTimeout(_options, hasFallbackValue); - data = await RunUtils.RunAsyncFuncWithTimeoutAsync( - async ct => await _cache.GetAsync(MaybeProcessCacheKey(key), ct).ConfigureAwait(false), + data = await RunUtils.RunAsyncFuncWithTimeoutAsync( + async ct => await GetCacheEntryAsync(MaybeProcessCacheKey(key), ct).ConfigureAwait(false), timeout.Value, true, token: token @@ -185,7 +196,7 @@ public async ValueTask SetEntryAsync(string operationId, string ke } } - data = null; + data = default; } if (data is null) @@ -204,11 +215,11 @@ public async ValueTask SetEntryAsync(string operationId, string ke FusionCacheDistributedEntry? entry; if (_options.PreferSyncSerialization) { - entry = _serializer.Deserialize>(data); + entry = Deserialize>(data); } else { - entry = await _serializer.DeserializeAsync>(data, token).ConfigureAwait(false); + entry = await DeserializeAsync>(data, token).ConfigureAwait(false); } var isValid = false; @@ -292,7 +303,7 @@ public async ValueTask RemoveEntryAsync(string operationId, string key, Fu key, async ct => { - await _cache.RemoveAsync(MaybeProcessCacheKey(key), ct).ConfigureAwait(false); + await RemoveCacheEntryAsync(MaybeProcessCacheKey(key), ct).ConfigureAwait(false); // EVENT _events.OnRemove(operationId, key); diff --git a/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Sync.cs b/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Sync.cs index 1f44607e..c0976fcc 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Sync.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Distributed/DistributedCacheAccessor_Sync.cs @@ -1,11 +1,22 @@ using System.Diagnostics; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using ZiggyCreatures.Caching.Fusion.Internals.Diagnostics; namespace ZiggyCreatures.Caching.Fusion.Internals.Distributed; -internal partial class DistributedCacheAccessor +internal abstract partial class DistributedCacheAccessor { + protected abstract TSerialized? Serialize(FusionCacheDistributedEntry obj); + + protected abstract T? Deserialize(TSerialized data); + + protected abstract TSerialized? GetCacheEntry(string key); + + protected abstract void SetCacheEntry(string key, TSerialized data, DistributedCacheEntryOptions distributedOptions); + + protected abstract void RemoveCacheEntry(string key); + private bool ExecuteOperation(string operationId, string key, Action action, string actionDescription, FusionCacheEntryOptions options, CancellationToken token) { //if (IsCurrentlyUsable(operationId, key) == false) @@ -70,13 +81,13 @@ public bool SetEntry(string operationId, string key, IFusionCacheEntry e var distributedEntry = entry.AsDistributedEntry(options); // SERIALIZATION - byte[]? data; + TSerialized? data; try { if (_logger?.IsEnabled(LogLevel.Trace) ?? false) _logger.Log(LogLevel.Trace, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId} K={CacheKey}): [DC] serializing the entry {Entry}", _options.CacheName, _options.InstanceId, operationId, key, distributedEntry.ToLogString(_options.IncludeTagsInLogs)); - data = _serializer.Serialize(distributedEntry); + data = Serialize(distributedEntry); } catch (Exception exc) { @@ -121,7 +132,7 @@ public bool SetEntry(string operationId, string key, IFusionCacheEntry e { var distributedOptions = options.ToDistributedCacheEntryOptions(_options, _logger, operationId, key); - _cache.Set(MaybeProcessCacheKey(key), data, distributedOptions); + SetCacheEntry(MaybeProcessCacheKey(key), data, distributedOptions); // EVENT _events.OnSet(operationId, key); @@ -147,12 +158,12 @@ public bool SetEntry(string operationId, string key, IFusionCacheEntry e using var activity = Activities.SourceDistributedLevel.StartActivityWithCommonTags(Activities.Names.DistributedGet, _options.CacheName, _options.InstanceId!, key, operationId, CacheLevelKind.Distributed); // GET FROM DISTRIBUTED CACHE - byte[]? data; + TSerialized? data; try { timeout ??= options.GetAppropriateDistributedCacheTimeout(_options, hasFallbackValue); - data = RunUtils.RunSyncFuncWithTimeout( - _ => _cache.Get(MaybeProcessCacheKey(key)), + data = RunUtils.RunSyncFuncWithTimeout( + _ => GetCacheEntry(MaybeProcessCacheKey(key)), timeout.Value, true, token: token @@ -178,7 +189,7 @@ public bool SetEntry(string operationId, string key, IFusionCacheEntry e } } - data = null; + data = default; } if (data is null) @@ -194,7 +205,7 @@ public bool SetEntry(string operationId, string key, IFusionCacheEntry e // DESERIALIZATION try { - var entry = _serializer.Deserialize>(data); + var entry = Deserialize>(data); var isValid = false; if (entry is null) { @@ -276,7 +287,7 @@ public bool RemoveEntry(string operationId, string key, FusionCacheEntryOptions key, _ => { - _cache.Remove(MaybeProcessCacheKey(key)); + RemoveCacheEntry(MaybeProcessCacheKey(key)); // EVENT _events.OnRemove(operationId, key); diff --git a/src/ZiggyCreatures.FusionCache/Internals/Distributed/IDistributedCacheAccessor.cs b/src/ZiggyCreatures.FusionCache/Internals/Distributed/IDistributedCacheAccessor.cs new file mode 100644 index 00000000..a085d06b --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Internals/Distributed/IDistributedCacheAccessor.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Caching.Distributed; +using ZiggyCreatures.Caching.Fusion.Serialization; + +namespace ZiggyCreatures.Caching.Fusion.Internals.Distributed; + +internal interface IDistributedCacheAccessor +{ + IDistributedCache DistributedCache { get; } + + IFusionCacheSerializer Serializer { get; } + + bool IsCurrentlyUsable(string? operationId, string? key); + + (FusionCacheDistributedEntry? entry, bool isValid) TryGetEntry(string operationId, string key, FusionCacheEntryOptions options, bool hasFallbackValue, TimeSpan? timeout, CancellationToken token); + + ValueTask<(FusionCacheDistributedEntry? entry, bool isValid)> TryGetEntryAsync(string operationId, string key, FusionCacheEntryOptions options, bool hasFallbackValue, TimeSpan? timeout, CancellationToken token); + + bool SetEntry(string operationId, string key, IFusionCacheEntry entry, FusionCacheEntryOptions options, bool isBackground, CancellationToken token); + + ValueTask SetEntryAsync(string operationId, string key, IFusionCacheEntry entry, FusionCacheEntryOptions options, bool isBackground, CancellationToken token); + + bool RemoveEntry(string operationId, string key, FusionCacheEntryOptions options, bool isBackground, CancellationToken token); + + ValueTask RemoveEntryAsync(string operationId, string key, FusionCacheEntryOptions options, bool isBackground, CancellationToken token); +} diff --git a/src/ZiggyCreatures.FusionCache/Internals/FusionCacheInternalUtils.cs b/src/ZiggyCreatures.FusionCache/Internals/FusionCacheInternalUtils.cs index b4129598..3cd9fa79 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/FusionCacheInternalUtils.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/FusionCacheInternalUtils.cs @@ -475,7 +475,7 @@ public static bool ShouldWrite(this MemoryCacheAccessor mca, FusionCacheEntryOpt return true; } - public static bool ShouldRead(this DistributedCacheAccessor? dca, FusionCacheEntryOptions options) + public static bool ShouldRead(this IDistributedCacheAccessor? dca, FusionCacheEntryOptions options) { if (dca is null) return false; @@ -486,7 +486,7 @@ public static bool ShouldRead(this DistributedCacheAccessor? dca, FusionCacheEnt return true; } - public static bool ShouldReadWhenStale(this DistributedCacheAccessor? dca, FusionCacheEntryOptions options) + public static bool ShouldReadWhenStale(this IDistributedCacheAccessor? dca, FusionCacheEntryOptions options) { if (dca is null) return false; @@ -497,7 +497,7 @@ public static bool ShouldReadWhenStale(this DistributedCacheAccessor? dca, Fusio return true; } - public static bool ShouldWrite(this DistributedCacheAccessor? dca, FusionCacheEntryOptions options) + public static bool ShouldWrite(this IDistributedCacheAccessor? dca, FusionCacheEntryOptions options) { if (dca is null) return false; @@ -508,7 +508,7 @@ public static bool ShouldWrite(this DistributedCacheAccessor? dca, FusionCacheEn return true; } - public static bool CanBeUsed(this DistributedCacheAccessor? dca, string? operationId, string? key) + public static bool CanBeUsed(this IDistributedCacheAccessor? dca, string? operationId, string? key) { if (dca is null) return false; diff --git a/src/ZiggyCreatures.FusionCache/Internals/Memory/FusionCacheMemoryEntry.cs b/src/ZiggyCreatures.FusionCache/Internals/Memory/FusionCacheMemoryEntry.cs index b801daf0..89cd52c4 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Memory/FusionCacheMemoryEntry.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Memory/FusionCacheMemoryEntry.cs @@ -172,7 +172,7 @@ public void UpdateFromDistributedEntry(FusionCacheDistributedEntry distr return cache.TryUpdateMemoryEntryFromDistributedEntryAsync(operationId, cacheKey, this); } - public ValueTask SetDistributedEntryAsync(string operationId, string key, DistributedCacheAccessor dca, FusionCacheEntryOptions options, bool isBackground, CancellationToken token) + public ValueTask SetDistributedEntryAsync(string operationId, string key, IDistributedCacheAccessor dca, FusionCacheEntryOptions options, bool isBackground, CancellationToken token) { return dca.SetEntryAsync(operationId, key, this, options, isBackground, token); } diff --git a/src/ZiggyCreatures.FusionCache/Internals/Memory/IFusionCacheMemoryEntry.cs b/src/ZiggyCreatures.FusionCache/Internals/Memory/IFusionCacheMemoryEntry.cs index 16eb7d29..84f88891 100644 --- a/src/ZiggyCreatures.FusionCache/Internals/Memory/IFusionCacheMemoryEntry.cs +++ b/src/ZiggyCreatures.FusionCache/Internals/Memory/IFusionCacheMemoryEntry.cs @@ -18,5 +18,5 @@ internal interface IFusionCacheMemoryEntry (bool error, bool isSame, bool hasUpdated) TryUpdateMemoryEntryFromDistributedEntry(string operationId, string cacheKey, FusionCache cache); ValueTask<(bool error, bool isSame, bool hasUpdated)> TryUpdateMemoryEntryFromDistributedEntryAsync(string operationId, string cacheKey, FusionCache cache); - ValueTask SetDistributedEntryAsync(string operationId, string key, DistributedCacheAccessor dca, FusionCacheEntryOptions options, bool isBackground, CancellationToken token); + ValueTask SetDistributedEntryAsync(string operationId, string key, IDistributedCacheAccessor dca, FusionCacheEntryOptions options, bool isBackground, CancellationToken token); } diff --git a/src/ZiggyCreatures.FusionCache/Internals/ReadOnlySequenceStream.cs b/src/ZiggyCreatures.FusionCache/Internals/ReadOnlySequenceStream.cs new file mode 100644 index 00000000..d76c0cb1 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Internals/ReadOnlySequenceStream.cs @@ -0,0 +1,168 @@ +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace ZiggyCreatures.Caching.Fusion.Internals; + +/// +/// An implementation of that wraps a and enables reading from it. +/// +public sealed class ReadOnlySequenceStream : Stream +{ + private readonly ReadOnlySequence _source; + private ReadOnlySequence _tail; + private bool _disposed; + + /// + /// Creates a from the provided . + /// + /// The sequence to read from. + public ReadOnlySequenceStream(in ReadOnlySequence source) + { + _source = source; + _tail = source; + } + + /// + public override bool CanRead => !_disposed; + + /// + public override bool CanWrite => false; + + /// + public override bool CanSeek => !_disposed; + + /// + public override long Length + { + get + { + ThrowIfDisposed(); + return _source.Length; + } + } + + /// + public override long Position + { + get + { + ThrowIfDisposed(); + return _source.Length - _tail.Length; + } + + set + { + ThrowIfDisposed(); + _tail = _source.Slice(value); + } + } + + /// + public override void Flush() + { + ThrowIfDisposed(); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + ThrowIfDisposed(); + + var position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => _source.Length - _tail.Length + offset, + SeekOrigin.End => _source.Length + offset, + _ => throw new ArgumentOutOfRangeException(nameof(origin), origin, message: null) + }; + + _tail = _source.Slice(position); + + return position; + } + + /// + public override void SetLength(long value) + { + ThrowIfDisposed(); + throw new NotSupportedException("Cannot set the length of a read-only stream."); + } + + /// + public override int Read(byte[]? buffer, int offset, int count) + { + var destination = buffer.AsSpan(offset, count); + return Read(destination); + } + + /// + public override int ReadByte() + { + Span buffer = stackalloc byte[1]; + + if (Read(buffer) == 0) + { + return -1; + } + + return buffer[0]; + } + +#if !NETSTANDARD2_0 + /// + public override int Read(Span buffer) +#else + private int Read(Span buffer) +#endif + { + ThrowIfDisposed(); + + var remainingLength = _tail.Length; + var bytesToRead = remainingLength >= buffer.Length ? buffer.Length : (int)remainingLength; + + if (bytesToRead == 0) + { + return 0; + } + + var endPosition = _tail.GetPosition(bytesToRead); + _tail.Slice(0, endPosition).CopyTo(buffer); + _tail = _tail.Slice(endPosition); + + return bytesToRead; + } + + /// + public override void Write(byte[]? buffer, int offset, int count) + { + throw new NotSupportedException("Cannot write to a read-only stream."); + } + + /// + public override void WriteByte(byte value) + { + throw new NotSupportedException("Cannot write to a read-only stream."); + } + + /// + protected override void Dispose(bool disposing) + { + _disposed = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ThrowIfDisposed() + { +#if NETSTANDARD2_0 + if (_disposed) ThrowObjectDisposedSlow(); + + static void ThrowObjectDisposedSlow() + { + throw new ObjectDisposedException(nameof(ReadOnlySequenceStream)); + } +#else + ObjectDisposedException.ThrowIf(_disposed, typeof(ReadOnlySequenceStream)); +#endif + } + +} diff --git a/src/ZiggyCreatures.FusionCache/Serialization/IBufferFusionCacheSerializer.cs b/src/ZiggyCreatures.FusionCache/Serialization/IBufferFusionCacheSerializer.cs new file mode 100644 index 00000000..71191979 --- /dev/null +++ b/src/ZiggyCreatures.FusionCache/Serialization/IBufferFusionCacheSerializer.cs @@ -0,0 +1,44 @@ +using System.Buffers; + +namespace ZiggyCreatures.Caching.Fusion.Serialization; + +/// +/// A generic serializer that converts any object instance to and from binary representation, using low-allocation primitives. +/// Used along the . +/// +public interface IBufferFusionCacheSerializer : IFusionCacheSerializer +{ + /// + /// Serializes the specified into the . + /// + /// The type of the parameter. + /// The object to serialize. + /// The target to write the serialized value. + void Serialize(T? obj, IBufferWriter destination); + + /// + /// Serializes the specified into the . + /// + /// The type of the parameter. + /// The object to serialize. + /// The target to write the serialized value. + /// The cancellation token. + ValueTask SerializeAsync(T? obj, IBufferWriter destination, CancellationToken token = default); + + /// + /// Deserialized the specified into an object of type . + /// + /// The type of the object to be returned. + /// The data to deserialize. + /// The deserialized object. + T? Deserialize(in ReadOnlySequence data); + + /// + /// Deserialized the specified into an object of type . + /// + /// The type of the object to be returned. + /// The data to deserialize. + /// The cancellation token. + /// The deserialized object. + ValueTask DeserializeAsync(ReadOnlySequence data, CancellationToken token = default); +} diff --git a/src/ZiggyCreatures.FusionCache/Serialization/IFusionCacheSerializer.cs b/src/ZiggyCreatures.FusionCache/Serialization/IFusionCacheSerializer.cs index 915ee5fb..ff7b6721 100644 --- a/src/ZiggyCreatures.FusionCache/Serialization/IFusionCacheSerializer.cs +++ b/src/ZiggyCreatures.FusionCache/Serialization/IFusionCacheSerializer.cs @@ -35,7 +35,7 @@ public interface IFusionCacheSerializer /// /// The type of the object to be returned. /// The data to deserialize. - /// /// The cancellation token. + /// The cancellation token. /// The deserialized object. ValueTask DeserializeAsync(byte[] data, CancellationToken token = default); } diff --git a/tests/ZiggyCreatures.FusionCache.Playground/Scenarios/OpenTelemetryScenario.cs b/tests/ZiggyCreatures.FusionCache.Playground/Scenarios/OpenTelemetryScenario.cs index 566dd03a..2b3ac35b 100644 --- a/tests/ZiggyCreatures.FusionCache.Playground/Scenarios/OpenTelemetryScenario.cs +++ b/tests/ZiggyCreatures.FusionCache.Playground/Scenarios/OpenTelemetryScenario.cs @@ -172,7 +172,7 @@ public static async Task RunAsync() //distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); // CHAOS + MEMORY - var chaosDistributedCache = new ChaosDistributedCache(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()))); + var chaosDistributedCache = ChaosDistributedCache.Create(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()))); chaosDistributedCache.SetAlwaysDelay(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(100)); distributedCache = chaosDistributedCache; diff --git a/tests/ZiggyCreatures.FusionCache.Simulator/Program.cs b/tests/ZiggyCreatures.FusionCache.Simulator/Program.cs index b1724b2d..a3246123 100644 --- a/tests/ZiggyCreatures.FusionCache.Simulator/Program.cs +++ b/tests/ZiggyCreatures.FusionCache.Simulator/Program.cs @@ -320,7 +320,7 @@ private static void SetupClusters(IServiceProvider serviceProvider, ILogger>() : null; - var tmp = new ChaosDistributedCache(distributedCache, chaosDistributedCacheLogger); + var tmp = ChaosDistributedCache.Create(distributedCache, chaosDistributedCacheLogger); if (SimulatorOptions.ChaosDistributedCacheSyntheticMinDelay is not null && SimulatorOptions.ChaosDistributedCacheSyntheticMaxDelay is not null) { tmp.SetAlwaysDelay(SimulatorOptions.ChaosDistributedCacheSyntheticMinDelay.Value, SimulatorOptions.ChaosDistributedCacheSyntheticMaxDelay.Value); diff --git a/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Async.cs index d1b11275..4a8714e9 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Async.cs @@ -27,7 +27,7 @@ private async Task CanRecoverAsync(SerializerType serializerType) var _value = 0; var key = "foo"; - var distributedCache = new ChaosDistributedCache(CreateDistributedCache(), CreateXUnitLogger()); + var distributedCache = ChaosDistributedCache.Create(CreateDistributedCache(), CreateXUnitLogger()); var backplaneConnectionId = Guid.NewGuid().ToString("N"); @@ -267,7 +267,7 @@ public async Task CanHandleIssuesWithBothDistributedCacheAndBackplaneAsync(Seria defaultOptions.AutoRecoveryDelay = TimeSpan.FromSeconds(1); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, logger: CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, logger: CreateXUnitLogger()); // SETUP CACHE A var backplaneA = new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId }); @@ -353,7 +353,7 @@ public async Task CanHandleReconnectedBackplaneWithoutReconnectedDistributedCach defaultOptions.AutoRecoveryDelay = TimeSpan.FromSeconds(1); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, logger: CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, logger: CreateXUnitLogger()); // SETUP CACHE A var backplaneA = new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId }); @@ -450,7 +450,7 @@ public async Task CanHandleDistributedCacheErrorsWithBackplaneRetryAsync(Seriali defaultOptions.AutoRecoveryDelay = TimeSpan.FromSeconds(1); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, logger: CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, logger: CreateXUnitLogger()); // SETUP CACHE A var backplaneA = new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId }); diff --git a/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Sync.cs index 072f30be..7fae2969 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/AutoRecoveryTests_Sync.cs @@ -27,7 +27,7 @@ private void CanRecover(SerializerType serializerType) var _value = 0; var key = "foo"; - var distributedCache = new ChaosDistributedCache(CreateDistributedCache(), CreateXUnitLogger()); + var distributedCache = ChaosDistributedCache.Create(CreateDistributedCache(), CreateXUnitLogger()); var backplaneConnectionId = Guid.NewGuid().ToString("N"); @@ -267,7 +267,7 @@ public void CanHandleIssuesWithBothDistributedCacheAndBackplane(SerializerType s defaultOptions.AutoRecoveryDelay = TimeSpan.FromSeconds(1); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, logger: CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, logger: CreateXUnitLogger()); // SETUP CACHE A var backplaneA = new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId }); @@ -353,7 +353,7 @@ public void CanHandleReconnectedBackplaneWithoutReconnectedDistributedCache(Seri defaultOptions.AutoRecoveryDelay = TimeSpan.FromSeconds(1); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, logger: CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, logger: CreateXUnitLogger()); // SETUP CACHE A var backplaneA = new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId }); @@ -450,7 +450,7 @@ public void CanHandleDistributedCacheErrorsWithBackplaneRetry(SerializerType ser defaultOptions.AutoRecoveryDelay = TimeSpan.FromSeconds(1); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, logger: CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, logger: CreateXUnitLogger()); // SETUP CACHE A var backplaneA = new MemoryBackplane(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId }); diff --git a/tests/ZiggyCreatures.FusionCache.Tests/DependencyInjectionTests.cs b/tests/ZiggyCreatures.FusionCache.Tests/DependencyInjectionTests.cs index 5b8e9a96..ecf3a4f1 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/DependencyInjectionTests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/DependencyInjectionTests.cs @@ -14,7 +14,6 @@ using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.Chaos; using ZiggyCreatures.Caching.Fusion.Internals.Backplane; -using ZiggyCreatures.Caching.Fusion.Internals.Distributed; using ZiggyCreatures.Caching.Fusion.Internals.DistributedLocker; using ZiggyCreatures.Caching.Fusion.Locking; using ZiggyCreatures.Caching.Fusion.Locking.Distributed; @@ -360,8 +359,8 @@ public void CanUseRegisteredDistributedCache() static IDistributedCache GetDistributedCache(IFusionCache cache) { - var dla = (DistributedCacheAccessor)(typeof(FusionCache).GetField("_dca", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(cache)!); - return dla.DistributedCache; + var dca = ((FusionCache)cache).DistributedCacheAccessor!; + return dca.DistributedCache; } var distributedCache = GetDistributedCache(cache); @@ -1289,10 +1288,10 @@ public void CanUseKeyedDistributedCache() var services = new ServiceCollection(); // NOTE: THIS SHOULD BE TRANSIENT, NOT SINGLETON: I'M DOING THIS ONLY FOR TESTING PURPOSES - var registeredSerializer = new ChaosSerializer(new FusionCacheSystemTextJsonSerializer()); + var registeredSerializer = ChaosSerializer.Create(new FusionCacheSystemTextJsonSerializer()); services.AddKeyedSingleton("FooSerializer", registeredSerializer); - var registeredDistributedCache = new ChaosDistributedCache(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()))); + var registeredDistributedCache = ChaosDistributedCache.Create(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()))); services.AddKeyedSingleton("FooDistributedCache", registeredDistributedCache); services.AddFusionCache() @@ -1422,10 +1421,10 @@ public void CanUseKeyedServices() services.AddKeyedSingleton("FooMemoryCache", registeredMemoryCache); // NOTE: THIS SHOULD BE TRANSIENT, NOT SINGLETON: I'M DOING THIS ONLY FOR TESTING PURPOSES - var registeredSerializer = new ChaosSerializer(new FusionCacheSystemTextJsonSerializer()); + var registeredSerializer = ChaosSerializer.Create(new FusionCacheSystemTextJsonSerializer()); services.AddKeyedSingleton("FooSerializer", registeredSerializer); - var registeredDistributedCache = new ChaosDistributedCache(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()))); + var registeredDistributedCache = ChaosDistributedCache.Create(new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()))); services.AddKeyedSingleton("FooDistributedCache", registeredDistributedCache); // NOTE: THIS SHOULD BE TRANSIENT, NOT SINGLETON: I'M DOING THIS ONLY FOR TESTING PURPOSES diff --git a/tests/ZiggyCreatures.FusionCache.Tests/FusionHybridCacheTests/HybridL1L2Tests.cs b/tests/ZiggyCreatures.FusionCache.Tests/FusionHybridCacheTests/HybridL1L2Tests.cs index 443decbf..2669cfd9 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/FusionHybridCacheTests/HybridL1L2Tests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/FusionHybridCacheTests/HybridL1L2Tests.cs @@ -86,7 +86,7 @@ public async Task HandlesDistributedCacheFailuresAsync(SerializerType serializer var keyFoo = CreateRandomCacheKey("foo"); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc); + var chaosdc = ChaosDistributedCache.Create(dc); var options = CreateFusionCacheOptions(); options.DefaultEntryOptions.IsFailSafeEnabled = true; using var fc = new FusionCache(options).SetupDistributedCache(chaosdc, TestsUtils.GetSerializer(serializerType)); @@ -125,7 +125,7 @@ public async Task HandlesDistributedCacheFailuresInTheMiddleOfAnOperationAsync(S using var mc = new MemoryCache(new MemoryCacheOptions()); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc); + var chaosdc = ChaosDistributedCache.Create(dc); var options = CreateFusionCacheOptions(); options.DistributedCacheKeyModifierMode = CacheKeyModifierMode.None; using var fc = new FusionCache(options, mc).SetupDistributedCache(chaosdc, TestsUtils.GetSerializer(serializerType)); @@ -163,7 +163,7 @@ public async Task AppliesDistributedCacheHardTimeoutAsync(SerializerType seriali var softTimeout = TimeSpan.FromMilliseconds(100); var hardTimeout = TimeSpan.FromMilliseconds(1_000); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc); + var chaosdc = ChaosDistributedCache.Create(dc); using var mc = new MemoryCache(new MemoryCacheOptions()); var options = CreateFusionCacheOptions(); @@ -199,7 +199,7 @@ public async Task AppliesDistributedCacheSoftTimeoutAsync(SerializerType seriali var duration = TimeSpan.FromSeconds(1); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc); + var chaosdc = ChaosDistributedCache.Create(dc); var options = CreateFusionCacheOptions(); options.DefaultEntryOptions.IsFailSafeEnabled = true; @@ -238,7 +238,7 @@ public async Task DistributedCacheCircuitBreakerActuallyWorksAsync(SerializerTyp var circuitBreakerDuration = TimeSpan.FromSeconds(2); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc); + var chaosdc = ChaosDistributedCache.Create(dc); using var mc = new MemoryCache(new MemoryCacheOptions()); var options = CreateFusionCacheOptions(); @@ -271,7 +271,7 @@ public async Task ReThrowsOriginalExceptionsAsync(SerializerType serializerType) var keyBar = CreateRandomCacheKey("bar"); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc); + var chaosdc = ChaosDistributedCache.Create(dc); chaosdc.SetAlwaysThrow(); var options = CreateFusionCacheOptions(); @@ -301,7 +301,7 @@ public async Task ReThrowsDistributedCacheExceptionsAsync(SerializerType seriali var keyBar = CreateRandomCacheKey("bar"); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc); + var chaosdc = ChaosDistributedCache.Create(dc); chaosdc.SetAlwaysThrow(); using var fc = new FusionCache(CreateFusionCacheOptions()); @@ -330,7 +330,7 @@ public async Task ReThrowsSerializationExceptionsAsync(SerializerType serializer options.DefaultEntryOptions.Duration = TimeSpan.FromMilliseconds(100); options.DefaultEntryOptions.DistributedCacheDuration = TimeSpan.FromSeconds(10); using var fc = new FusionCache(options, logger: logger); - var serializer = new ChaosSerializer(TestsUtils.GetSerializer(serializerType)); + var serializer = ChaosSerializer.Create(TestsUtils.GetSerializer(serializerType)); var dc = CreateDistributedCache(); fc.SetupDistributedCache(dc, serializer); @@ -628,7 +628,7 @@ public async Task EagerRefreshDoesNotBlockAsync(SerializerType serializerType) var eagerRefreshThreshold = 0.2f; var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc, CreateXUnitLogger()); + var chaosdc = ChaosDistributedCache.Create(dc, CreateXUnitLogger()); var options = CreateFusionCacheOptions(); options.DefaultEntryOptions.Duration = duration; @@ -717,7 +717,7 @@ public async Task CanExecuteBackgroundDistributedCacheOperationsAsync(Serializer var logger = CreateXUnitLogger(); using var mc = new MemoryCache(new MemoryCacheOptions()); var dc = CreateDistributedCache(); - var chaosdc = new ChaosDistributedCache(dc, CreateXUnitLogger()); + var chaosdc = ChaosDistributedCache.Create(dc, CreateXUnitLogger()); var options = CreateFusionCacheOptions(); options.DefaultEntryOptions.Duration = TimeSpan.FromSeconds(10); options.DefaultEntryOptions.AllowBackgroundDistributedCacheOperations = true; diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs index 16e9d25f..4965c9a3 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Async.cs @@ -355,7 +355,7 @@ public async Task CanExecuteBackgroundBackplaneOperationsAsync(SerializerType se using var fusionCache = new FusionCache(options, memoryCache, logger); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, CreateXUnitLogger()); fusionCache.SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); var backplane = new MemoryBackplane(Options.Create(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId })); diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs index 7e9bee11..b19b7f4d 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2BackplaneTests_Sync.cs @@ -355,7 +355,7 @@ public void CanExecuteBackgroundBackplaneOperations(SerializerType serializerTyp using var fusionCache = new FusionCache(options, memoryCache, logger); var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, CreateXUnitLogger()); fusionCache.SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); var backplane = new MemoryBackplane(Options.Create(new MemoryBackplaneOptions() { ConnectionId = backplaneConnectionId })); diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests.cs index e2a1c6c4..4be7daa0 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests.cs @@ -1,24 +1,12 @@ using FusionCacheTests.Stuff; using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Caching.StackExchangeRedis; -using Microsoft.Extensions.Options; using Xunit; using ZiggyCreatures.Caching.Fusion; namespace FusionCacheTests; -public partial class L1L2Tests - : AbstractTests +public abstract partial class L1L2Tests(ITestOutputHelper output) : AbstractTests(output, "MyCache") { - private static readonly bool UseRedis = false; - private static readonly string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; - - public L1L2Tests(ITestOutputHelper output) - : base(output, "MyCache:") - { - } - private FusionCacheOptions CreateFusionCacheOptions(string? cacheName = null, Action? configure = null) { var res = new FusionCacheOptions @@ -37,13 +25,7 @@ private FusionCacheOptions CreateFusionCacheOptions(string? cacheName = null, Ac return res; } - private static IDistributedCache CreateDistributedCache() - { - if (UseRedis) - return new RedisCache(new RedisCacheOptions() { Configuration = RedisConnection }); - - return new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); - } + protected abstract IDistributedCache CreateDistributedCache(); private static string CreateRandomCacheName(string cacheName) { diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Async.cs index 28b382fa..e733ad69 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Async.cs @@ -37,7 +37,7 @@ public async Task HandlesDistributedCacheFailuresAsync(SerializerType serializer var keyFoo = CreateRandomCacheKey("foo"); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); using var fusionCache = new FusionCache(CreateFusionCacheOptions()).SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); fusionCache.DefaultEntryOptions.AllowBackgroundDistributedCacheOperations = false; var initialValue = await fusionCache.GetOrSetAsync(keyFoo, _ => Task.FromResult(42), new FusionCacheEntryOptions() { Duration = TimeSpan.FromSeconds(1), IsFailSafeEnabled = true }, token: TestContext.Current.CancellationToken); @@ -57,7 +57,7 @@ public async Task AppliesDistributedCacheHardTimeoutAsync(SerializerType seriali var softTimeout = TimeSpan.FromMilliseconds(100); var hardTimeout = TimeSpan.FromMilliseconds(1_000); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); using var fusionCache = new FusionCache(CreateFusionCacheOptions(), memoryCache); @@ -86,7 +86,7 @@ public async Task AppliesDistributedCacheSoftTimeoutAsync(SerializerType seriali var duration = TimeSpan.FromSeconds(1); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); var options = CreateFusionCacheOptions(); options.TagsDefaultEntryOptions.DistributedCacheSoftTimeout = softTimeout; @@ -120,7 +120,7 @@ public async Task DistributedCacheCircuitBreakerActuallyWorksAsync(SerializerTyp var circuitBreakerDuration = TimeSpan.FromSeconds(2); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); var options = CreateFusionCacheOptions(); @@ -195,7 +195,7 @@ public async Task ReThrowsOriginalExceptionsAsync(SerializerType serializerType) var keyBar = CreateRandomCacheKey("bar"); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); chaosDistributedCache.SetAlwaysThrow(); var options = CreateFusionCacheOptions(); @@ -224,7 +224,7 @@ public async Task ReThrowsDistributedCacheExceptionsAsync(SerializerType seriali var keyBar = CreateRandomCacheKey("bar"); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); chaosDistributedCache.SetAlwaysThrow(); using var fusionCache = new FusionCache(CreateFusionCacheOptions()); @@ -249,7 +249,7 @@ public async Task ReThrowsSerializationExceptionsAsync(SerializerType serializer { var logger = CreateXUnitLogger(); using var cache = new FusionCache(CreateFusionCacheOptions(CreateRandomCacheName("foo")), logger: logger); - var serializer = new ChaosSerializer(TestsUtils.GetSerializer(serializerType)); + var serializer = ChaosSerializer.Create(TestsUtils.GetSerializer(serializerType)); var distributedCache = CreateDistributedCache(); cache.SetupDistributedCache(distributedCache, serializer); @@ -395,7 +395,7 @@ public async Task CanSkipDistributedCacheOnReadOnlyAsync(SerializerType serializ { var keyFoo = CreateRandomCacheKey("foo"); - var dc = new ChaosDistributedCache(CreateDistributedCache()); + var dc = ChaosDistributedCache.Create(CreateDistributedCache()); dc.SetAlwaysThrow(); using var fc = new FusionCache(CreateFusionCacheOptions()).SetupDistributedCache(dc, TestsUtils.GetSerializer(serializerType)); @@ -600,7 +600,7 @@ public async Task EagerRefreshDoesNotBlockAsync(SerializerType serializerType) var eagerRefreshThreshold = 0.2f; var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, CreateXUnitLogger()); using var cache = new FusionCache(CreateFusionCacheOptions(), logger: CreateXUnitLogger()); cache.SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); @@ -682,7 +682,7 @@ public async Task CanExecuteBackgroundDistributedCacheOperationsAsync(Serializer var logger = CreateXUnitLogger(); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, CreateXUnitLogger()); using var fusionCache = new FusionCache(CreateFusionCacheOptions(), memoryCache, logger); fusionCache.SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); @@ -1102,7 +1102,7 @@ public async Task HandlesDistributedCacheFailuresInTheMiddleOfAnOperationAsync(S using var memoryCache = new MemoryCache(new MemoryCacheOptions()); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); var options = CreateFusionCacheOptions(); options.DistributedCacheKeyModifierMode = CacheKeyModifierMode.None; using var fusionCache = new FusionCache(options, memoryCache).SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); @@ -1137,7 +1137,7 @@ public async Task MemoryCacheDurationIsRespectedAsync(SerializerType serializerT var memoryCacheDuration = TimeSpan.FromSeconds(1); var dcache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(dcache); + var chaosDistributedCache = ChaosDistributedCache.Create(dcache); var options = CreateFusionCacheOptions(Guid.NewGuid().ToString("N")); options.DefaultEntryOptions = new FusionCacheEntryOptions { @@ -1225,7 +1225,7 @@ public async Task DistributedLockerWorksAsync(SerializerType serializerType) var simulatedFactoryDuration = TimeSpan.FromSeconds(4); var dcache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(dcache); + var chaosDistributedCache = ChaosDistributedCache.Create(dcache); var options = CreateFusionCacheOptions(FusionCacheInternalUtils.GenerateOperationId()); options.DefaultEntryOptions = new FusionCacheEntryOptions { @@ -1291,7 +1291,7 @@ public async Task DistributedLockerWorksWithEagerRefreshAsync(SerializerType ser var duration = TimeSpan.FromSeconds(10); var dcache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(dcache); + var chaosDistributedCache = ChaosDistributedCache.Create(dcache); var options = CreateFusionCacheOptions(FusionCacheInternalUtils.GenerateOperationId()); options.DefaultEntryOptions = new FusionCacheEntryOptions { diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Buffer.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Buffer.cs new file mode 100644 index 00000000..bf663802 --- /dev/null +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Buffer.cs @@ -0,0 +1,53 @@ +using System.Buffers; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Xunit; + +namespace FusionCacheTests; + +public sealed class L1L2Tests_Buffer(ITestOutputHelper output) : L1L2Tests(output) +{ + protected override IDistributedCache CreateDistributedCache() + { + return new BufferMemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); + } + + private sealed class BufferMemoryDistributedCache(IOptions optionsAccessor) + : MemoryDistributedCache(optionsAccessor), IBufferDistributedCache + { + public bool TryGet(string key, IBufferWriter destination) + { + var bytes = Get(key); + if (bytes is null) + { + return false; + } + + destination.Write(bytes); + return true; + } + + public async ValueTask TryGetAsync(string key, IBufferWriter destination, CancellationToken token) + { + var bytes = await GetAsync(key, token); + if (bytes is null) + { + return false; + } + + destination.Write(bytes); + return true; + } + + public void Set(string key, ReadOnlySequence value, DistributedCacheEntryOptions options) + { + Set(key, value.ToArray(), options); + } + + public ValueTask SetAsync(string key, ReadOnlySequence value, DistributedCacheEntryOptions options, CancellationToken token) + { + return new ValueTask(SetAsync(key, value.ToArray(), options, token)); + } + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Classic.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Classic.cs new file mode 100644 index 00000000..89b32841 --- /dev/null +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Classic.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Xunit; + +namespace FusionCacheTests; + +public sealed class L1L2Tests_Classic(ITestOutputHelper output) : L1L2Tests(output) +{ + protected override IDistributedCache CreateDistributedCache() + { + return new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Redis.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Redis.cs new file mode 100644 index 00000000..23f62b05 --- /dev/null +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Redis.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.StackExchangeRedis; +using Xunit; + +namespace FusionCacheTests; + +#if REDIS_TESTS + +public sealed class L1L2Tests_Redis(ITestOutputHelper output) : L1L2Tests(output) +{ + private const string RedisConnection = "127.0.0.1:6379,ssl=False,abortConnect=false,connectTimeout=1000,syncTimeout=1000"; + + protected override IDistributedCache CreateDistributedCache() + { + return new RedisCache(new RedisCacheOptions() { Configuration = RedisConnection }); + + } +} + +#endif diff --git a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Sync.cs index 0dacb6d1..74a1b383 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/L1L2Tests_Sync.cs @@ -36,7 +36,7 @@ public void HandlesDistributedCacheFailures(SerializerType serializerType) var keyFoo = CreateRandomCacheKey("foo"); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); using var fusionCache = new FusionCache(CreateFusionCacheOptions()).SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); fusionCache.DefaultEntryOptions.AllowBackgroundDistributedCacheOperations = false; var initialValue = fusionCache.GetOrSet(keyFoo, _ => 42, new FusionCacheEntryOptions() { Duration = TimeSpan.FromSeconds(1), IsFailSafeEnabled = true }, token: TestContext.Current.CancellationToken); @@ -56,7 +56,7 @@ public void AppliesDistributedCacheHardTimeout(SerializerType serializerType) var softTimeout = TimeSpan.FromMilliseconds(100); var hardTimeout = TimeSpan.FromMilliseconds(1_000); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); using var fusionCache = new FusionCache(CreateFusionCacheOptions(), memoryCache); @@ -85,7 +85,7 @@ public void AppliesDistributedCacheSoftTimeout(SerializerType serializerType) var duration = TimeSpan.FromSeconds(1); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); var options = CreateFusionCacheOptions(); options.TagsDefaultEntryOptions.DistributedCacheSoftTimeout = softTimeout; @@ -119,7 +119,7 @@ public void DistributedCacheCircuitBreakerActuallyWorks(SerializerType serialize var circuitBreakerDuration = TimeSpan.FromSeconds(2); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); var options = CreateFusionCacheOptions(); @@ -195,7 +195,7 @@ public void ReThrowsOriginalExceptions(SerializerType serializerType) var keyBar = CreateRandomCacheKey("bar"); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); chaosDistributedCache.SetAlwaysThrow(); var options = CreateFusionCacheOptions(); @@ -224,7 +224,7 @@ public void ReThrowsDistributedCacheExceptions(SerializerType serializerType) var keyBar = CreateRandomCacheKey("bar"); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache); chaosDistributedCache.SetAlwaysThrow(); using var fusionCache = new FusionCache(CreateFusionCacheOptions()); @@ -249,7 +249,7 @@ public void ReThrowsSerializationExceptions(SerializerType serializerType) { var logger = CreateXUnitLogger(); using var cache = new FusionCache(CreateFusionCacheOptions(CreateRandomCacheName("foo")), logger: logger); - var serializer = new ChaosSerializer(TestsUtils.GetSerializer(serializerType)); + var serializer = ChaosSerializer.Create(TestsUtils.GetSerializer(serializerType)); var distributedCache = CreateDistributedCache(); cache.SetupDistributedCache(distributedCache, serializer); @@ -395,7 +395,7 @@ public void CanSkipDistributedCacheOnReadOnly(SerializerType serializerType) { var keyFoo = CreateRandomCacheKey("foo"); - var dc = new ChaosDistributedCache(CreateDistributedCache()); + var dc = ChaosDistributedCache.Create(CreateDistributedCache()); dc.SetAlwaysThrow(); using var fc = new FusionCache(CreateFusionCacheOptions()).SetupDistributedCache(dc, TestsUtils.GetSerializer(serializerType)); @@ -600,7 +600,7 @@ public void EagerRefreshDoesNotBlock(SerializerType serializerType) var eagerRefreshThreshold = 0.2f; var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, CreateXUnitLogger()); using var cache = new FusionCache(CreateFusionCacheOptions(), logger: CreateXUnitLogger()); cache.SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); @@ -682,7 +682,7 @@ public void CanExecuteBackgroundDistributedCacheOperations(SerializerType serial var logger = CreateXUnitLogger(); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); var distributedCache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(distributedCache, CreateXUnitLogger()); + var chaosDistributedCache = ChaosDistributedCache.Create(distributedCache, CreateXUnitLogger()); using var fusionCache = new FusionCache(CreateFusionCacheOptions(), memoryCache, logger); fusionCache.SetupDistributedCache(chaosDistributedCache, TestsUtils.GetSerializer(serializerType)); @@ -1066,7 +1066,7 @@ public void MemoryCacheDurationIsRespected(SerializerType serializerType) var memoryCacheDuration = TimeSpan.FromSeconds(1); var dcache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(dcache); + var chaosDistributedCache = ChaosDistributedCache.Create(dcache); var options = CreateFusionCacheOptions(Guid.NewGuid().ToString("N")); options.DefaultEntryOptions = new FusionCacheEntryOptions { @@ -1154,7 +1154,7 @@ public void DistributedLockerWorks(SerializerType serializerType) var simulatedFactoryDuration = TimeSpan.FromSeconds(4); var dcache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(dcache); + var chaosDistributedCache = ChaosDistributedCache.Create(dcache); var options = CreateFusionCacheOptions(FusionCacheInternalUtils.GenerateOperationId()); options.DefaultEntryOptions = new FusionCacheEntryOptions { @@ -1220,7 +1220,7 @@ public void DistributedLockerWorksWithEagerRefresh(SerializerType serializerType var duration = TimeSpan.FromSeconds(10); var dcache = CreateDistributedCache(); - var chaosDistributedCache = new ChaosDistributedCache(dcache); + var chaosDistributedCache = ChaosDistributedCache.Create(dcache); var options = CreateFusionCacheOptions(FusionCacheInternalUtils.GenerateOperationId()); options.DefaultEntryOptions = new FusionCacheEntryOptions { diff --git a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests.cs b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase.cs similarity index 75% rename from tests/ZiggyCreatures.FusionCache.Tests/SerializationTests.cs rename to tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase.cs index f8b6c2fe..73fd2962 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase.cs @@ -3,12 +3,12 @@ namespace FusionCacheTests; -public partial class SerializationTests +public partial class SerializationTestsBase : AbstractTests { private static readonly ComplexType[] BigData; - static SerializationTests() + static SerializationTestsBase() { var len = 1024 * 1024; BigData = new ComplexType[len]; @@ -18,7 +18,7 @@ static SerializationTests() } } - public SerializationTests(ITestOutputHelper output) + public SerializationTestsBase(ITestOutputHelper output) : base(output, null) { } diff --git a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Async.cs b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase_Async.cs similarity index 84% rename from tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Async.cs rename to tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase_Async.cs index 2847cffb..56af4b9e 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Async.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase_Async.cs @@ -7,15 +7,21 @@ namespace FusionCacheTests; -public partial class SerializationTests +public abstract partial class SerializationTestsBase : AbstractTests { - private static async Task LoopDeLoopAsync(IFusionCacheSerializer serializer, T? obj) + private async Task LoopDeLoopAsync(IBufferFusionCacheSerializer serializer, T? obj) { - var data = await serializer.SerializeAsync(obj); - return await serializer.DeserializeAsync(data); + var data = await SerializeAsync(serializer, obj, TestContext.Current.CancellationToken); + return await DeserializeAsync(serializer, data, TestContext.Current.CancellationToken); } + protected abstract ValueTask SerializeAsync(IBufferFusionCacheSerializer serializer, T? obj, + CancellationToken ct); + + protected abstract ValueTask DeserializeAsync(IBufferFusionCacheSerializer serializer, byte[] data, + CancellationToken ct); + [Theory] [ClassData(typeof(SerializerTypesClassData))] public async Task LoopSucceedsWithSimpleTypesAsync(SerializerType serializerType) @@ -61,12 +67,12 @@ public async Task LoopSucceedsWithDistributedEntryAndSimpleTypesAsync(Serializer var now = DateTimeOffset.UtcNow; var obj = new FusionCacheDistributedEntry(SampleString, now.UtcTicks, now.AddSeconds(10).UtcTicks, [], new FusionCacheEntryMetadata(true, now.AddSeconds(9).UtcTicks, "abc123", now.UtcTicks, 123, 1)); - var data = await serializer.SerializeAsync(obj, TestContext.Current.CancellationToken); + var data = await SerializeAsync(serializer, obj, TestContext.Current.CancellationToken); Assert.NotNull(data); Assert.NotEmpty(data); - var looped = await serializer.DeserializeAsync>(data, TestContext.Current.CancellationToken); + var looped = await DeserializeAsync>(serializer, data, TestContext.Current.CancellationToken); Assert.NotNull(looped); Assert.Equal(obj.Value, looped.Value); Assert.Equal(obj.Timestamp, looped.Timestamp); @@ -87,12 +93,12 @@ public async Task LoopSucceedsWithDistributedEntryAndNoMetadataAsync(SerializerT var now = DateTimeOffset.UtcNow; var obj = new FusionCacheDistributedEntry(SampleString, now.UtcTicks, now.AddSeconds(10).UtcTicks, [], null); - var data = await serializer.SerializeAsync(obj, TestContext.Current.CancellationToken); + var data = await SerializeAsync(serializer, obj, TestContext.Current.CancellationToken); Assert.NotNull(data); Assert.NotEmpty(data); - var looped = await serializer.DeserializeAsync>(data, TestContext.Current.CancellationToken); + var looped = await DeserializeAsync>(serializer, data, TestContext.Current.CancellationToken); Assert.NotNull(looped); Assert.Equal(obj.Value, looped.Value); Assert.Equal(obj.Timestamp, looped.Timestamp); @@ -108,12 +114,12 @@ public async Task LoopSucceedsWithDistributedEntryAndComplexTypesAsync(Serialize var now = DateTimeOffset.UtcNow; var obj = new FusionCacheDistributedEntry(ComplexType.CreateSample(), now.UtcTicks, now.AddSeconds(10).AddMicroseconds(now.Nanosecond * -1).UtcTicks, [], new FusionCacheEntryMetadata(true, now.AddSeconds(9).AddMicroseconds(now.Microsecond * -1).UtcTicks, "abc123", now.AddMicroseconds(now.Microsecond * -1).UtcTicks, 123, 1)); - var data = await serializer.SerializeAsync(obj, TestContext.Current.CancellationToken); + var data = await SerializeAsync(serializer, obj, TestContext.Current.CancellationToken); Assert.NotNull(data); Assert.NotEmpty(data); - var looped = await serializer.DeserializeAsync>(data, TestContext.Current.CancellationToken); + var looped = await DeserializeAsync>(serializer, data, TestContext.Current.CancellationToken); Assert.NotNull(looped); Assert.Equal(obj.Value, looped.Value); Assert.Equal(obj.Timestamp, looped.Timestamp); @@ -148,10 +154,10 @@ public async Task CanWorkWithByteArraysAsync(SerializerType serializerType) null ); - var serializedData = await serializer.SerializeAsync(sourceEntry, TestContext.Current.CancellationToken); + var serializedData = await SerializeAsync(serializer, sourceEntry, TestContext.Current.CancellationToken); logger.LogInformation("SERIALIZED DATA: {bytes} bytes (+{delta} bytes)", serializedData.Length, serializedData.Length - sourceData.Length); - var targetEntry = await serializer.DeserializeAsync>(serializedData, TestContext.Current.CancellationToken); + var targetEntry = await DeserializeAsync>(serializer, serializedData, TestContext.Current.CancellationToken); logger.LogInformation("TARGET DATA: {bytes} bytes", targetEntry!.Value.Length); Assert.Equal(sourceData, targetEntry.Value); diff --git a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Sync.cs b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase_Sync.cs similarity index 85% rename from tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Sync.cs rename to tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase_Sync.cs index 6ecbf7e7..18485ce0 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Sync.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTestsBase_Sync.cs @@ -7,13 +7,17 @@ namespace FusionCacheTests; -public partial class SerializationTests +public abstract partial class SerializationTestsBase : AbstractTests { - private static T? LoopDeLoop(IFusionCacheSerializer serializer, T? obj) + protected abstract byte[] Serialize(IBufferFusionCacheSerializer serializer, T sourceEntry); + + protected abstract T? Deserialize(IBufferFusionCacheSerializer serializer, byte[] serializedData); + + private T? LoopDeLoop(IBufferFusionCacheSerializer serializer, T? obj) { - var data = serializer.Serialize(obj); - return serializer.Deserialize(data); + var data = Serialize(serializer, obj); + return Deserialize(serializer, data); } [Theory] @@ -61,12 +65,12 @@ public void LoopSucceedsWithDistributedEntryAndSimpleTypes(SerializerType serial var now = DateTimeOffset.UtcNow; var obj = new FusionCacheDistributedEntry(SampleString, now.UtcTicks, now.AddSeconds(10).UtcTicks, [], new FusionCacheEntryMetadata(true, now.AddSeconds(9).UtcTicks, "abc123", now.UtcTicks, 123, 1)); - var data = serializer.Serialize(obj); + var data = Serialize(serializer, obj); Assert.NotNull(data); Assert.NotEmpty(data); - var looped = serializer.Deserialize>(data); + var looped = Deserialize>(serializer, data); Assert.NotNull(looped); Assert.Equal(obj.Value, looped.Value); Assert.Equal(obj.Timestamp, looped.Timestamp); @@ -87,12 +91,12 @@ public void LoopSucceedsWithDistributedEntryAndNoMetadata(SerializerType seriali var now = DateTimeOffset.UtcNow; var obj = new FusionCacheDistributedEntry(SampleString, now.UtcTicks, now.AddSeconds(10).UtcTicks, [], null); - var data = serializer.Serialize(obj); + var data = Serialize(serializer, obj); Assert.NotNull(data); Assert.NotEmpty(data); - var looped = serializer.Deserialize>(data); + var looped = Deserialize>(serializer, data); Assert.NotNull(looped); Assert.Equal(obj.Value, looped.Value); Assert.Equal(obj.Timestamp, looped.Timestamp); @@ -108,12 +112,12 @@ public void LoopSucceedsWithDistributedEntryAndComplexTypes(SerializerType seria var now = DateTimeOffset.UtcNow; var obj = new FusionCacheDistributedEntry(ComplexType.CreateSample(), now.UtcTicks, now.AddSeconds(10).AddMicroseconds(now.Nanosecond * -1).UtcTicks, [], new FusionCacheEntryMetadata(true, now.AddSeconds(9).AddMicroseconds(now.Microsecond * -1).UtcTicks, "abc123", now.AddMicroseconds(now.Microsecond * -1).UtcTicks, 123, 1)); - var data = serializer.Serialize(obj); + var data = Serialize(serializer, obj); Assert.NotNull(data); Assert.NotEmpty(data); - var looped = serializer.Deserialize>(data); + var looped = Deserialize>(serializer, data); Assert.NotNull(looped); Assert.Equal(obj.Value, looped.Value); Assert.Equal(obj.Timestamp, looped.Timestamp); @@ -148,10 +152,10 @@ public void CanWorkWithByteArrays(SerializerType serializerType) null ); - var serializedData = serializer.Serialize(sourceEntry); + var serializedData = Serialize(serializer, sourceEntry); logger.LogInformation("SERIALIZED DATA: {bytes} bytes (+{delta} bytes)", serializedData.Length, serializedData.Length - sourceData.Length); - var targetEntry = serializer.Deserialize>(serializedData); + var targetEntry = Deserialize>(serializer, serializedData); logger.LogInformation("TARGET DATA: {bytes} bytes", targetEntry!.Value.Length); Assert.Equal(sourceData, targetEntry.Value); diff --git a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Buffered.cs b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Buffered.cs new file mode 100644 index 00000000..5a76768f --- /dev/null +++ b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Buffered.cs @@ -0,0 +1,50 @@ +using System.Buffers; +using Xunit; +using ZiggyCreatures.Caching.Fusion.Internals; +using ZiggyCreatures.Caching.Fusion.Serialization; + +namespace FusionCacheTests; + +public sealed class SerializationTests_Buffered(ITestOutputHelper output) : SerializationTestsBase(output) +{ + private static readonly Random Rnd = new(); + + protected override byte[] Serialize(IBufferFusionCacheSerializer serializer, T sourceEntry) + { + using var writer = new ArrayPoolBufferWriter(); + serializer.Serialize(sourceEntry, writer); + return writer.ToArray(); + } + + protected override T? Deserialize(IBufferFusionCacheSerializer serializer, byte[] serializedData) + where T : default + { + return serializer.Deserialize(new ReadOnlySequence(serializedData)); + } + + protected override async ValueTask SerializeAsync(IBufferFusionCacheSerializer serializer, T? obj, + CancellationToken ct) + where T : default + { + using var writer = new ArrayPoolBufferWriter(); + await serializer.SerializeAsync(obj, writer, ct); + return writer.ToArray(); + } + + protected override async ValueTask DeserializeAsync(IBufferFusionCacheSerializer serializer, byte[] data, CancellationToken ct) + where T : default + { + return await serializer.DeserializeAsync(new ReadOnlySequence(data), ct); + } + + private sealed class Segment() : ReadOnlySequenceSegment + { + public Segment(Memory memory, int index) : this() + { + Memory = memory; + RunningIndex = index; + } + + public void SetNext(Segment next) => Next = next; + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Classic.cs b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Classic.cs new file mode 100644 index 00000000..f22f5b2d --- /dev/null +++ b/tests/ZiggyCreatures.FusionCache.Tests/SerializationTests_Classic.cs @@ -0,0 +1,30 @@ +using Xunit; +using ZiggyCreatures.Caching.Fusion.Serialization; + +namespace FusionCacheTests; + +public sealed class SerializationTests_Classic(ITestOutputHelper output) : SerializationTestsBase(output) +{ + protected override byte[] Serialize(IBufferFusionCacheSerializer serializer, T sourceEntry) + { + return serializer.Serialize(sourceEntry); + } + + protected override T? Deserialize(IBufferFusionCacheSerializer serializer, byte[] serializedData) + where T : default + { + return serializer.Deserialize(serializedData); + } + + protected override ValueTask SerializeAsync(IBufferFusionCacheSerializer serializer, T? obj, CancellationToken ct) + where T : default + { + return serializer.SerializeAsync(obj, ct); + } + + protected override ValueTask DeserializeAsync(IBufferFusionCacheSerializer serializer, byte[] data, CancellationToken ct) + where T : default + { + return serializer.DeserializeAsync(data, ct); + } +} diff --git a/tests/ZiggyCreatures.FusionCache.Tests/Stuff/TestsUtils.cs b/tests/ZiggyCreatures.FusionCache.Tests/Stuff/TestsUtils.cs index a5962b45..f4067a23 100644 --- a/tests/ZiggyCreatures.FusionCache.Tests/Stuff/TestsUtils.cs +++ b/tests/ZiggyCreatures.FusionCache.Tests/Stuff/TestsUtils.cs @@ -8,7 +8,6 @@ using ZiggyCreatures.Caching.Fusion.Backplane; using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis; using ZiggyCreatures.Caching.Fusion.Internals.Backplane; -using ZiggyCreatures.Caching.Fusion.Internals.Distributed; using ZiggyCreatures.Caching.Fusion.Locking; using ZiggyCreatures.Caching.Fusion.Locking.AsyncKeyed; using ZiggyCreatures.Caching.Fusion.Plugins; @@ -24,7 +23,7 @@ namespace FusionCacheTests.Stuff; public static class TestsUtils { - public static IFusionCacheSerializer GetSerializer(SerializerType serializerType) + public static IBufferFusionCacheSerializer GetSerializer(SerializerType serializerType) { switch (serializerType) { @@ -116,21 +115,15 @@ public static FusionCacheOptions GetOptions(this IFusionCache cache) public static IFusionCacheSerializer? GetSerializer(IFusionCache cache) { - var dca = typeof(FusionCache).GetField("_dca", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(cache) as DistributedCacheAccessor; - if (dca is null) - return null; - - return typeof(DistributedCacheAccessor).GetField("_serializer", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(dca) as IFusionCacheSerializer; + var dca = ((FusionCache)cache).DistributedCacheAccessor; + return dca?.Serializer; } public static IDistributedCache? GetDistributedCache(IFusionCache cache) where TDistributedCache : class, IDistributedCache { - var dca = typeof(FusionCache).GetField("_dca", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(cache) as DistributedCacheAccessor; - if (dca is null) - return null; - - return typeof(DistributedCacheAccessor).GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(dca) as TDistributedCache; + var dca = ((FusionCache)cache).DistributedCacheAccessor; + return dca?.DistributedCache as TDistributedCache; } public static TBackplane? GetBackplane(IFusionCache cache)