Skip to content

Commit

Permalink
Catch and log serialization exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
Turnerj committed Apr 7, 2023
1 parent b052178 commit 50d4415
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 82 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Text;
using Newtonsoft.Json;

Expand Down Expand Up @@ -32,16 +33,30 @@ public NewtonsoftJsonCacheSerializer(JsonSerializerSettings settings)
/// <inheritdoc />
public void Serialize<T>(Stream stream, T? value)
{
using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true);
using var jsonWriter = new JsonTextWriter(streamWriter);
serializer.Serialize(jsonWriter, value);
try
{
using var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true);
using var jsonWriter = new JsonTextWriter(streamWriter);
serializer.Serialize(jsonWriter, value);
}
catch (Exception ex)
{
throw new CacheSerializationException("A serialization error has occurred when serializing with Newtonsoft.Json", ex);
}
}

/// <inheritdoc />
public T? Deserialize<T>(Stream stream)
{
using var streamReader = new StreamReader(stream, Encoding.UTF8, false, 1024);
using var jsonReader = new JsonTextReader(streamReader);
return serializer.Deserialize<T>(jsonReader);
try
{
using var streamReader = new StreamReader(stream, Encoding.UTF8, false, 1024);
using var jsonReader = new JsonTextReader(streamReader);
return serializer.Deserialize<T>(jsonReader);
}
catch (Exception ex)
{
throw new CacheSerializationException("A serialization error has occurred when deserializing with Newtonsoft.Json", ex);
}
}
}
25 changes: 20 additions & 5 deletions src/CacheTower.Serializers.Protobuf/ProtobufCacheSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using CacheTower.Providers.FileSystem;
using ProtoBuf;
using ProtoBuf.Meta;
Expand Down Expand Up @@ -49,15 +50,29 @@ public static void EnsureConfigured() { }
/// <inheritdoc />
public void Serialize<T>(Stream stream, T? value)
{
SerializerConfig<T>.EnsureConfigured();
Serializer.Serialize(stream, value);
try
{
SerializerConfig<T>.EnsureConfigured();
Serializer.Serialize(stream, value);
}
catch (Exception ex)
{
throw new CacheSerializationException("A serialization error has occurred when serializing with ProtoBuf", ex);
}
}

/// <inheritdoc />
public T? Deserialize<T>(Stream stream)
{
SerializerConfig<T>.EnsureConfigured();
return Serializer.Deserialize<T>(stream);
try
{
SerializerConfig<T>.EnsureConfigured();
return Serializer.Deserialize<T>(stream);
}
catch (Exception ex)
{
throw new CacheSerializationException("A serialization error has occurred when deserializing with ProtoBuf", ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Text.Json;

namespace CacheTower.Serializers.SystemTextJson;
Expand Down Expand Up @@ -28,12 +29,26 @@ public SystemTextJsonCacheSerializer(JsonSerializerOptions options)
/// <inheritdoc/>
public void Serialize<T>(Stream stream, T? value)
{
JsonSerializer.Serialize(stream, value, options);
try
{
JsonSerializer.Serialize(stream, value, options);
}
catch (Exception ex)
{
throw new CacheSerializationException("A serialization error has occurred when serializing with System.Text.Json", ex);
}
}

/// <inheritdoc/>
public T? Deserialize<T>(Stream stream)
{
return JsonSerializer.Deserialize<T>(stream, options);
try
{
return JsonSerializer.Deserialize<T>(stream, options);
}
catch (Exception ex)
{
throw new CacheSerializationException("A serialization error has occurred when deserializing with System.Text.Json", ex);
}
}
}
35 changes: 0 additions & 35 deletions src/CacheTower/CacheContextActivators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,4 @@ public void Dispose()
{
ServiceScope.Dispose();
}
}


internal class FuncCacheContextActivator<TContext> : ICacheContextActivator
{
private readonly Func<TContext> Resolver;

public FuncCacheContextActivator(Func<TContext> resolver)
{
Resolver = resolver;
}

public ICacheContextScope BeginScope()
{
return new FuncCacheContextScope<TContext>(Resolver);
}
}

internal class FuncCacheContextScope<TContext> : ICacheContextScope
{
private readonly Func<TContext> Resolver;

public FuncCacheContextScope(Func<TContext> resolver)
{
Resolver = resolver;
}

public object Resolve(Type type)
{
return Resolver()!;
}

public void Dispose()
{
}
}
43 changes: 35 additions & 8 deletions src/CacheTower/CacheStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Threading.Tasks;
using CacheTower.Extensions;
using CacheTower.Internal;
using CacheTower.Serializers;
using Microsoft.Extensions.Logging;

namespace CacheTower
{
Expand All @@ -15,21 +17,24 @@ public class CacheStack : ICacheStack, IFlushableCacheStack, IExtendableCacheSta
private bool Disposed;

private readonly CacheEntryKeyLock KeyLock = new();
private readonly ILogger<CacheStack> Logger;
private readonly ICacheLayer[] CacheLayers;
private readonly ExtensionContainer Extensions;

/// <summary>
/// Creates a new <see cref="CacheStack"/> with the given <paramref name="cacheLayers"/> and <paramref name="extensions"/>.
/// </summary>
/// <param name="logger">The internal logger to use.</param>
/// <param name="cacheLayers">The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required.</param>
/// <param name="extensions">The cache extensions to use for the current cache stack.</param>
public CacheStack(ICacheLayer[] cacheLayers, ICacheExtension[] extensions)
public CacheStack(ILogger<CacheStack> logger, ICacheLayer[] cacheLayers, ICacheExtension[] extensions)
{
if (cacheLayers == null || cacheLayers.Length == 0)
{
throw new ArgumentException("There must be at least one cache layer", nameof(cacheLayers));
}

Logger = logger;
CacheLayers = cacheLayers;

Extensions = new ExtensionContainer(extensions);
Expand Down Expand Up @@ -139,7 +144,15 @@ private async ValueTask InternalSetAsync<T>(string cacheKey, CacheEntry<T> cache
for (int i = 0, l = CacheLayers.Length; i < l; i++)
{
var layer = CacheLayers[i];
await layer.SetAsync(cacheKey, cacheEntry);

try
{
await layer.SetAsync(cacheKey, cacheEntry);
}
catch (CacheSerializationException ex)
{
Logger.LogWarning(ex, "Unable to set CacheKey={CacheKey} on CacheLayer={CacheLayer} due to an exception. This will result in cache misses on this layer.", cacheKey, layer.GetType());
}
}

await Extensions.OnCacheUpdateAsync(cacheKey, cacheEntry.Expiry, cacheUpdateType);
Expand All @@ -160,10 +173,17 @@ private async ValueTask InternalSetAsync<T>(string cacheKey, CacheEntry<T> cache
var layer = CacheLayers[layerIndex];
if (await layer.IsAvailableAsync(cacheKey))
{
var cacheEntry = await layer.GetAsync<T>(cacheKey);
if (cacheEntry != default)
try
{
var cacheEntry = await layer.GetAsync<T>(cacheKey);
if (cacheEntry != default)
{
return cacheEntry;
}
}
catch (CacheSerializationException ex)
{
return cacheEntry;
Logger.LogWarning(ex, "Unable to retrieve CacheKey={CacheKey} from CacheLayer={CacheLayer} due to an exception. This layer will be skipped.", cacheKey, layer.GetType());
}
}
}
Expand All @@ -179,10 +199,17 @@ private async ValueTask InternalSetAsync<T>(string cacheKey, CacheEntry<T> cache
var layer = CacheLayers[layerIndex];
if (await layer.IsAvailableAsync(cacheKey))
{
var cacheEntry = await layer.GetAsync<T>(cacheKey);
if (cacheEntry != default)
try
{
var cacheEntry = await layer.GetAsync<T>(cacheKey);
if (cacheEntry != default)
{
return (layerIndex, cacheEntry);
}
}
catch (CacheSerializationException ex)
{
return (layerIndex, cacheEntry);
Logger.LogWarning(ex, "Unable to retrieve CacheKey={CacheKey} from CacheLayer={CacheLayer} due to an exception. This layer will be skipped.", cacheKey, layer.GetType());
}
}
}
Expand Down
17 changes: 3 additions & 14 deletions src/CacheTower/CacheStack{TContext}.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace CacheTower
{
Expand All @@ -13,26 +14,14 @@ public class CacheStack<TContext> : CacheStack, ICacheStack<TContext>
{
private readonly ICacheContextActivator CacheContextActivator;

/// <summary>
/// Creates a new <see cref="CacheStack{TContext}"/> with the given <paramref name="contextFactory"/>, <paramref name="cacheLayers"/> and <paramref name="extensions"/>.
/// </summary>
/// <param name="contextFactory">The factory that provides the context. This is called for every cache item refresh.</param>
/// <param name="cacheLayers">The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required.</param>
/// <param name="extensions">The cache extensions to use for the current cache stack.</param>
[Obsolete("Use other constructor that requires an `ICacheContextActivator` instead. This constructor will be removed in a future version.")]
public CacheStack(Func<TContext> contextFactory, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(cacheLayers, extensions)
{
var factory = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory));
CacheContextActivator = new FuncCacheContextActivator<TContext>(factory);
}

/// <summary>
/// Creates a new <see cref="CacheStack{TContext}"/> with the given <paramref name="cacheContextActivator"/>, <paramref name="cacheLayers"/> and <paramref name="extensions"/>.
/// </summary>
/// <param name="logger">The internal logger to use.</param>
/// <param name="cacheContextActivator">The activator that provides the context. This is called for every cache item refresh.</param>
/// <param name="cacheLayers">The cache layers to use for the current cache stack. The layers should be ordered from the highest priority to the lowest. At least one cache layer is required.</param>
/// <param name="extensions">The cache extensions to use for the current cache stack.</param>
public CacheStack(ICacheContextActivator cacheContextActivator, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(cacheLayers, extensions)
public CacheStack(ILogger<CacheStack> logger, ICacheContextActivator cacheContextActivator, ICacheLayer[] cacheLayers, ICacheExtension[] extensions) : base(logger, cacheLayers, extensions)
{
CacheContextActivator = cacheContextActivator ?? throw new ArgumentNullException(nameof(cacheContextActivator));
}
Expand Down
1 change: 1 addition & 0 deletions src/CacheTower/CacheTower.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Condition="'$(TargetFramework)' == 'netstandard2.0'" Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="System.Memory" Version="4.5.5" />
Expand Down
25 changes: 25 additions & 0 deletions src/CacheTower/Serializers/CacheSerializationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace CacheTower.Serializers;

/// <summary>
/// An exception for any cache serialization exceptions that occur.
/// </summary>
public class CacheSerializationException : Exception
{
/// <summary>
/// Creates a new <see cref="CacheSerializationException"/>.
/// </summary>
public CacheSerializationException() : base() { }
/// <summary>
/// Creates a new <see cref="CacheSerializationException"/> with the specified <paramref name="message"/>.
/// </summary>
/// <param name="message">The error message</param>
public CacheSerializationException(string message) : base(message) { }
/// <summary>
/// Creates a new <see cref="CacheSerializationException"/> with the specified <paramref name="message"/> and <paramref name="innerException"/>.
/// </summary>
/// <param name="message">The error message</param>
/// <param name="innerException">The inner exception</param>
public CacheSerializationException(string message, Exception innerException) : base(message, innerException) { }
}
3 changes: 3 additions & 0 deletions src/CacheTower/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using CacheTower.Providers.FileSystem;
using CacheTower.Providers.Memory;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;

namespace Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -80,6 +81,7 @@ private static ICacheStack BuildCacheStack(IServiceProvider provider, Action<ISe
configureBuilder(provider, builder);
ThrowIfInvalidBuilder(builder);
return new CacheStack(
provider.GetRequiredService<ILogger<CacheStack>>(),
builder.CacheLayers.ToArray(),
builder.Extensions.ToArray()
);
Expand All @@ -91,6 +93,7 @@ private static ICacheStack<TContext> BuildCacheStack<TContext>(IServiceProvider
configureBuilder(provider, builder);
ThrowIfInvalidBuilder(builder);
return new CacheStack<TContext>(
provider.GetRequiredService<ILogger<CacheStack>>(),
builder.CacheContextActivator,
builder.CacheLayers.ToArray(),
builder.Extensions.ToArray()
Expand Down
Loading

0 comments on commit 50d4415

Please sign in to comment.