| File: Internal\DefaultHybridCache.Serialization.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.Caching.Hybrid\Microsoft.Extensions.Caching.Hybrid.csproj (Microsoft.Extensions.Caching.Hybrid) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.Caching.Hybrid.Internal; internal partial class DefaultHybridCache { // Per instance cache of typed serializers; each serializer is a // IHybridCacheSerializer<T> for the corresponding Type, but we can't // know which here - and undesirable to add an artificial non-generic // IHybridCacheSerializer base that serves no other purpose. private readonly ConcurrentDictionary<Type, object> _serializers = new(); internal int MaximumPayloadBytes { get; } internal IHybridCacheSerializer<T> GetSerializer<T>() { return _serializers.TryGetValue(typeof(T), out object? serializer) ? Unsafe.As<IHybridCacheSerializer<T>>(serializer) : ResolveAndAddSerializer(this); static IHybridCacheSerializer<T> ResolveAndAddSerializer(DefaultHybridCache @this) { // It isn't critical that we get only one serializer instance during start-up; what matters // is that we don't get a new serializer instance *every time*. IHybridCacheSerializer<T>? serializer = @this._services.GetService<IHybridCacheSerializer<T>>(); if (serializer is null) { foreach (IHybridCacheSerializerFactory factory in @this._serializerFactories) { if (factory.TryCreateSerializer<T>(out IHybridCacheSerializer<T>? current)) { serializer = current; break; // we've already reversed the factories, so: the first hit is what we want } } } if (serializer is null) { throw new InvalidOperationException($"No {nameof(IHybridCacheSerializer<T>)} configured for type '{typeof(T).Name}'"); } // store the result so we don't repeat this in future @this._serializers[typeof(T)] = serializer; return serializer; } } private bool TrySerialize<T>(T value, out BufferChunk buffer, out IHybridCacheSerializer<T>? serializer) { // note: also returns the serializer we resolved, because most-any time we want to serialize, we'll also want // to make sure we use that same instance later (without needing to re-resolve and/or store the entire HC machinery) RecyclableArrayBufferWriter<byte>? writer = null; buffer = default; try { writer = RecyclableArrayBufferWriter<byte>.Create(MaximumPayloadBytes); // note this lifetime spans the SetL2Async serializer = GetSerializer<T>(); serializer.Serialize(value, writer); buffer = new(writer.DetachCommitted(out int length), 0, length, returnToPool: true); // remove buffer ownership from the writer writer.Dispose(); // we're done with the writer return true; } catch (Exception ex) { bool knownCause = false; // ^^^ if we know what happened, we can record directly via cause-specific events // and treat as a handled failure (i.e. return false) - otherwise, we'll bubble // the fault up a few layers *in addition to* logging in a failure event if (writer is not null) { if (writer.QuotaExceeded) { _logger.MaximumPayloadBytesExceeded(ex, MaximumPayloadBytes); knownCause = true; } writer.Dispose(); } if (!knownCause) { _logger.SerializationFailure(ex); throw; } buffer = default; serializer = null; return false; } } }