New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
try/catch in Lambda Registration Doesn't Reset the Activation Stack #929
Comments
I've spent a couple of hours reducing your example much further. Figuring out what was going on was a lot easier once some moving parts were removed; a true "minimal repro" really helped. Here's the much simpler version that still reproduces the issue. using System;
using Autofac;
using Autofac.Builder;
using Autofac.Core;
namespace Sample
{
public interface IService
{
}
public class MyWorker
{
public SomeDependency dependency;
public IService service;
public MyWorker(IService service, SomeDependency dependency)
{
this.service = service;
this.dependency = dependency;
}
}
public static class Program
{
public static IContainer PrepareContainer()
{
var builder = new ContainerBuilder();
builder.RegisterType<SimpleService>().AsSelf();
builder.RegisterGeneratedFactory<SimpleService.Factory>(new TypedService(typeof(SimpleService)));
builder.Register<IService>(c =>
{
// Comment out this try/catch to enable things to work.
try
{
return c.Resolve<SimpleService>();
}
catch (Exception)
{
}
var factory = c.Resolve<SimpleService.Factory>();
return factory.Invoke(Guid.Empty);
});
builder.RegisterType<SomeDependency>().AsSelf();
builder.RegisterType<MyWorker>().AsSelf();
return builder.Build();
}
private static void Main(string[] args)
{
var container = PrepareContainer();
using (var scope = container.BeginLifetimeScope())
{
scope.Resolve<MyWorker>();
}
Console.ReadLine();
}
}
public class SimpleService : IService
{
private Guid id;
public SimpleService(Guid id)
{
this.id = id;
}
public delegate SimpleService Factory(Guid id);
}
public class SomeDependency
{
private IService service;
public SomeDependency(IService service)
{
this.service = service;
}
}
} You don't need:
Once that's all simplified down, it's much easier to see into what's going on by doing a little debugging. Something important to know is that for each resolve operation ( In a working scenario (where the try/catch is commented out) the activation stack gets reset for each dependency. It looks like:
In a failing scenario (as seen above, with the try/catch enabled) the activation stack for the resolve context isn't getting reset because it's assumed that if a
By doing the try/catch, you're not allowing the resolve operation to abort; you're disengaging the safety mechanism. You can see how the activation stack assumes it can push/pop based on the relative success or failure of a complete resolve op inside It looks like I can switch try
{
var instance = activation.Execute();
_successfulActivations.Add(activation);
return instance;
}
finally
{
_activationStack.Pop();
if (_activationStack.Count == 0)
CompleteActivations();
--_callDepth;
} I'll work on that, however, I would recommend that you also implement a workaround to keep yourself safe from some of the side effects here - instead of resolving the try/catch stuff from the context, you can resolve from the same lifetime scope in a different resolve op: builder.Register<IService>(c =>
{
try
{
// Resolve an ILifetimeScope, which will give you the current scope,
// then resolve the service from that. It should still allow sharing component
// lifetimes, but will start a new resolve operation instead of chaining onto
// the current op. Note this will _also_ possibly break the built-in circular
// dependency detection, so it will be on you to choose if this is the right
// solution to go with.
return c.Resolve<ILifetimeScope>().Resolve<SimpleService>();
}
catch (Exception)
{
}
var factory = c.Resolve<SimpleService.Factory>();
return factory.Invoke(Guid.Empty);
}); |
As it turns out, the example can be even further simplified by removing the generated factory. Instead of calling the factory at the end of the lambda, just |
Greetings,
We are running into a Circular Dependency exception when using lambda registration along with some transitive dependencies and manual resolution of components.
This issue happens when the following 2 factors are present:
It seems Autofac gets in a state that leads to the circular dependency exception when DependecyResolutionException is thrown inside the lambda registratin. It doesn't matter that DependecyResolutionException is handled - as long as it gets thrown - the circular dependency issue happens.
To fully showcase the issue I've created a sample solution.
Partial code (with registration and resolution) is as follows:
P.S.: I've looked through the opened issues and didn't see the lambda registration use case. Apologies if I did miss it (found few other use cases for Circular Dependency).
The text was updated successfully, but these errors were encountered: