FailSafe - Not Working As Expected #460
-
Morning! I've been looking at FusionCache for some work I'm doing, and so far it's been great! But I'm a bit stumped as to what's happening when we turn on FailSafe. I've set up a unit test with a fully initiated version of FusionCache, and have a test which will get from an inner method (successfully), then wait some time, then try again (after the cache entity has been moved from active to expired, but not removed). Then I throw an exception from the inner method... but I get the exception bubbled up out of the method I'm testing, rather than the expired entity back, which is what I thought would happen according to the docs? public class CacheAccessor : IAccessor
{
private readonly IFusionCache fusionCache;
private readonly IAccessor inner;
private readonly CacheOptions configurationOptions;
public CacheAccessor(
IFusionCacheProvider fusionCacheProvider,
IOptions<CacheOptions> options,
IAccessor inner)
{
configurationOptions = options.Value;
fusionCache = fusionCacheProvider.GetCache(CacheConstants.CacheName);
this.inner = inner;
}
Task<T> IAccessor.GetConfiguration<T>(int customerId, CancellationToken token)
{
var typeName = typeof(T).FullName;
return fusionCache.GetOrSet(
key: $"CustomerConfigSingle-{typeName}-{customerId}",
factory: _ => inner.GetConfiguration<T>(customerId, token),
setupAction: options => options
.SetDuration(configurationOptions.CacheDuration)
.SetFailSafe(
isEnabled: true,
maxDuration: configurationOptions.ExpiredCacheDuration,
throttleDuration: configurationOptions.FailSafeThrottleDuration),
token: token);
}
}
public class CacheAccessorTests
{
private Mock<IAccessor> Inner { get; } = new();
private IAccessor CacheAccessor { get; }
public CacheAccessorTests()
{
var factory = LoggerFactory.Create(b => b
.SetMinimumLevel(LogLevel.Trace)
.AddSimpleConsole(options => options.IncludeScopes = true)
);
var logger = factory.CreateLogger<FusionCache>();
var provider = new Mock<IFusionCacheProvider>();
Inner = new Mock<IAccessor>();
var fusionCache = new FusionCache(
new FusionCacheOptions { FailSafeActivationLogLevel = LogLevel.Trace }, logger: logger);
provider
.Setup(x => x.GetCache(It.IsAny<string>()))
.Returns(fusionCache);
var mockOptions = Options.Create(new CacheOptions
{
CacheDuration = TimeSpan.FromSeconds(3),
ExpiredCacheDuration = TimeSpan.FromMinutes(2),
FailSafeThrottleDuration = TimeSpan.FromSeconds(5)
});
CacheAccessor = new CacheAccessor(
provider.Object,
mockOptions,
Inner.Object);
}
[Fact]
public async Task GetConfiguration_ByCustomerId_AndFails_ShouldReturnCachedValue()
{
var customerId = 123;
var expectedConfig = new TestConfiguration { Name = "TestCustomer" };
Inner
.Setup(x => x.GetConfiguration<TestConfiguration>(customerId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedConfig);
var result1 = await CacheAccessor.GetConfiguration<TestConfiguration>(
customerId, CancellationToken.None);
result1.Should().BeEquivalentTo(expectedConfig);
await Task.Delay(TimeSpan.FromSeconds(4));
var exception = new InvalidOperationException("Test exception");
Inner
.Setup(x => x.GetConfiguration<TestConfiguration>(customerId, It.IsAny<CancellationToken>()))
.ThrowsAsync(exception);
var result2 = await CacheAccessor.GetConfiguration<TestConfiguration>(
customerId, CancellationToken.None);
result2.Should().BeEquivalentTo(expectedConfig);
Inner
.Verify(
x => x.GetConfiguration<TestConfiguration>(
customerId,
It.IsAny<CancellationToken>()),
Times.Exactly(2));
}
private class TestConfiguration
{
public string Name { get; set; }
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 3 replies
-
Hi @bendursley , on which versions of FusionCache/.NET/OS/etc are you? |
Beta Was this translation helpful? Give feedback.
-
Anyway, as an MRE you can try this: var fc = new FusionCache(new FusionCacheOptions());
var v1 = fc.GetOrSet<int>(
"foo",
_ => 123,
options => options
.SetDuration(TimeSpan.FromSeconds(3))
.SetFailSafe(true, TimeSpan.FromMinutes(2), TimeSpan.FromSeconds(5))
);
Console.WriteLine($"v1: {v1}");
await Task.Delay(TimeSpan.FromSeconds(4));
var v2 = fc.GetOrSet<int>(
"foo",
_ => throw new Exception("BOOM!"),
options => options
.SetDuration(TimeSpan.FromSeconds(3))
.SetFailSafe(true, TimeSpan.FromMinutes(2), TimeSpan.FromSeconds(5))
);
Console.WriteLine($"v2: {v2}"); For which you can see that the output is:
As soon as you disable fail-safe on the 2nd call, you'll get this output:
Maybe it's something with the mocking part? |
Beta Was this translation helpful? Give feedback.
-
Interesting, I'll have a look at this today, thanks for responding @jodydonetti ! We're on the latest version of FusionCache, and .NET 8. We're also using Moq for our mocking framework |
Beta Was this translation helpful? Give feedback.
-
@jodydonetti It was indeed the mock, made a mistake and a fresh set of eyes caught it. Thanks for the help! |
Beta Was this translation helpful? Give feedback.
Anyway, as an MRE you can try this:
For which you can see that the output is:
As soon as you disable fail-safe on the 2nd call, you…