2

I run into a runtime error when using autofac in combination with hybrid cache and redis. I hope someone can explain why this error occurs.

When using autofac to resolve an instance of HybridCache in combination with redis, autofac gives a DependencyResolutionException that DefaultHybridCache is trying to create another instance of itself. This happens when trying to resolve an instance of HybridcCache. Full error:

DependencyResolutionException: The constructor of type 'Microsoft.Extensions.Caching.Hybrid.Internal.DefaultHybridCache' attempted to create another instance of itself. This is not permitted because the service is configured to only allowed a single instance per lifetime scope.

This happens when I register redis with a shared multiplexer. I'm not sure why this matters, see below for reproduction code. There is no error when using the default DI system of .NET.

So short question is, is this a bug in autofac (or the redis/hybridcache library) or am I using the libraries wrong?

Minimal reproduction is as follows: Adding autofac to a blazor server application set up as below gives the exception when resolving HyrbidCache.

using Autofac.Extensions.DependencyInjection;
using BlazorAppAutofac.Components;
using BlazorAppAutofac.Services;
using StackExchange.Redis;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddHybridCache();

IConnectionMultiplexer connectionMultiplexer = ConnectionMultiplexer.Connect(new ConfigurationOptions
{
    EndPoints = { $"localhost:6379" },
    AbortOnConnectFail = false,
});
builder.Services.AddSingleton<IConnectionMultiplexer>(connectionMultiplexer);
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.ConnectionMultiplexerFactory = () => Task.FromResult(connectionMultiplexer);
    options.InstanceName = "BlazorAppAutofac";
});

builder.Services.AddTransient<IMessageService, MessageService>();

When using the below to register redis (so not adding the multiplexer) does not give an exception.

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("localhost:6379");
    options.InstanceName = "BlazorAppAutofac";
});

In Home.razor

@inject IMessageService MessageService

In MessageService.cs

public class MessageService : IMessageService
{
    private readonly HybridCache _cache;

    public MessageService(HybridCache cache)
    {
        _cache = cache;
    }
}

I would have expected both options for registration to work as they also work with the default dependency injection.

Interestingly enough in the code of StackExchangeRedis there seems to be some code that tries to circumvent the problem.

internal sealed class RedisCacheImpl : RedisCache
{
    private readonly IServiceProvider _services;

    internal override bool IsHybridCacheActive()
        => _services.GetService<HybridCache>() is not null;

    public RedisCacheImpl(IOptions<RedisCacheOptions> optionsAccessor, ILogger<RedisCache> logger, IServiceProvider services)
        : base(optionsAccessor, logger)
    {
        _services = services; // important: do not check for HybridCache here due to dependency - creates a cycle
    }

    public RedisCacheImpl(IOptions<RedisCacheOptions> optionsAccessor, IServiceProvider services)
        : base(optionsAccessor)
    {
        _services = services; // important: do not check for HybridCache here due to dependency - creates a cycle
    }
}

Stacktrace:

DependencyResolutionException: The constructor of type 'Microsoft.Extensions.Caching.Hybrid.Internal.DefaultHybridCache' attempted to create another instance of itself. This is not permitted because the service is configured to only allowed a single instance per lifetime scope.
Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid id, Func<object> creator)
Autofac.Core.Lifetime.LifetimeScope.CreateSharedInstance(Guid primaryId, Nullable<Guid> qualifyingId, Func<object> creator)
Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext context)
Autofac.Core.Resolving.ResolveOperation.InvokePipeline(ref ResolveRequest request, DefaultResolveRequestContext requestContext)
Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ref ResolveRequest request)
Autofac.Core.Resolving.ResolveOperation.Autofac.Core.Resolving.IResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ref ResolveRequest request)
Autofac.Core.Resolving.Pipeline.DefaultResolveRequestContext.ResolveComponent(ref ResolveRequest request)
Autofac.Core.Activators.Reflection.AutowiringParameter+<>c__DisplayClass0_0.<CanSupplyValue>b__0()
Autofac.Core.Activators.Reflection.BoundConstructor.Instantiate()
Autofac.Core.Activators.Reflection.ReflectionActivator+<>c__DisplayClass14_0.<UseSingleConstructorActivation>b__0(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Middleware.DelegateMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Extensions.DependencyInjection.KeyedServiceMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder+<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext context)
Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
1
  • 1
    The code for this library lives here. Your best chance might be creating an issue there or in the repo for StackExchange Redis. Commented Oct 13 at 12:34

1 Answer 1

1

This is not your misuse. With a shared IConnectionMultiplexer, the Redis cache implementation probes for HybridCache via IServiceProvider (IsHybridCacheActive()), and HybridCache (singleton) is being built at that exact moment → re-entrant resolve of the same singleton.
MS.DI tolerates this pattern; Autofac forbids it and throws: “attempted to create another instance of itself… single instance per lifetime scope."

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.