1

Original:

Summary: I'm having an issue with Autofac, where objects which are registered as singletons/'SingleInstance', are having their dependencies that are registered as 'InstancePerLifetimeScope' are ending up becoming 'singletons' after the resolution of the first singleton is resolved.

Details: I'm working on a project to incorporate Autofac (version #4.9.2) into a windows service which is responsible for running background tasks. In preparation I've tried reading everything I can that involves lifetime scopes. My code has Autofac resolve service objects, where each service has it's own expected role and function, and/or sometimes interact with other services (currently there are no circular dependencies). To do this I'm registering these services as Singletons and then resolving/starting each in a sequential order. By registering services as Singletons, if another service has an dependency on a already resolved service, rather then instantiate a new service Autofac returns the already resolved one.

The problem I'm running into is, there are instances where services have dependencies that when resolved it's expected that the dependency will be reused across the whole service (my example is a logger). However, it's also expected that the resolved dependency should be different in each service, as each service's resolved dependency could have different behavior. To do this I was registering these dependencies as 'InstancePerLifetimeScope'. However, when the service is resolved, the service's dependency which is registered as 'InstancePerLifetimeScope' end up in Autofac's root container which is retrieved for all other services that share the same dependency rather then resolving the dependency as a new object.

Example: I've placed an simplified example below that's similar to my code that replicates and outlines how the problem is being hit.

// Use of AutoFac with Established Components
var builder = new ContainerBuilder();

// Register
builder.RegisterType<Logger>().As<ILogger>().InstancePerLifetimeScope();
builder.RegisterType<Service>().Named<IService>("first").SingleInstance();
builder.RegisterType<Service>().Named<IService>("second").SingleInstance();

// Build
var container = builder.Build();

// Resolve
IService firstService = null;
using (ILifetimeScope firstScope = container.BeginLifetimeScope())
{
   firstService = firstScope.ResolveNamed<IService>("first");
};
IService secondService = null;
using (ILifetimeScope secondScope = container.BeginLifetimeScope())
{
   secondService = secondScope.ResolveNamed<IService>("second");
};

// Test

// This case is false, both services are different
if (firstService == secondService)
{
   throw new Exception("Services were the same");
}

// This case is true, each service shares the same logger
if (((Service)firstService)._logger == ((Service)secondService)._logger)
{
   throw new Exception("Loggers were the same");
}

public interface ILogger
{
   void Log(string message);
}

public interface IService
{
   void Run();
}

public class Logger : ILogger
{
   public Logger()
   {
      Console.WriteLine("Create: Logger");
   }

   public void Log(string message)
   {
      Console.WriteLine(message);
   }
}

public class Service : IService
{
   public readonly ILogger _logger;

   public Service(ILogger logger)
   {
      Console.WriteLine("Create: Service");
      _logger = logger;
   }

   public void Run()
   {
      Console.WriteLine("Run");
   }
}

Conclusion: I'd expect that when an object is registered as a 'InstancePerLifetimeScope', it would always be tied to the current child scope regardless of if the object it is being resolved for is registered as a 'SingleInstance'. However, instead it seems the resolved dependency is being placed in to the root scope.

Is there anything I can do to enforce 'InstancePerLifetimeScope' registration dependencies to be exactly that for objects that are resolved for a 'SingleInstance', so these dependencies don't end up in the root container and reused by other services?

Edit 08/05/2019:

From John King link which points to the subject of Captive Dependencies, I've reviewed the topic and agree that my issue falls into the realm of Captive Dependencies. However, I am surprised by how the matter is handled, in that captive dependencies just exists as a known issue across all IOC libraries, and there seems to be little to no hope of discussing different solutions to mitigate the issue within a update. Despite that I still think the problem I'm hitting is a bit different from the examples outlined in Autofac's documentation and the other resources which AutoFac references for how this issue can occur. My reasoning for this argument is that I believe the real problem here stems from "InstancePerLifetimeScope", so I'm wondering if this warrants the possibility of another solution.

The examples given by Autofac and the link to Mark Seemann blog post (Which is a good resource to understand captive dependency, just with a bad example), try to show a captive dependency for a singleton occurring which has an dependency that's registered as "Instance Per Dependency". The problem is that this example (shown below) is wrong in pointing out the actual problem. "ProductService" is registered as a singleton, so after it's resolved the first time it'll always return the same "ProductService" reference which the example implies is not the case.

Incorrect Example Linked by AutoFac Doc

// Example from https://blog.ploeh.dk/2014/06/02/captive-dependency/
var builder = new ContainerBuilder();
builder.RegisterType<ProductService>().SingleInstance();
builder.RegisterType<SqlProductRepository>().As<IProductRepository>();
builder.RegisterType<CommerceContext>();
var container = builder.Build();

var actual1 = container.Resolve<ProductService>();
var actual2 = container.Resolve<ProductService>();

// You'd want this assertion to pass, but it fails
Assert.NotEqual(actual1.Repository, actual2.Repository);

// Lightbarrier snippet: Ya but guess what this also fails
Assert.NotEqual(actual1, actual2);

From the above code it's implied that captive dependency can occur for an "Instance Per Dependency" registration however, as shown by the below example that's not the case.

Example of what's actually happening

var builder = new ContainerBuilder();

// Register
builder.RegisterType<Logger>().As<ILogger>(); // InstancePerDependency
builder.RegisterType<Test.Service.Service1>().As<IService1>().SingleInstance();
builder.RegisterType<Test.Service.Service2>().As<IService2>().SingleInstance();

// Build
var container = builder.Build();

// Resolve
IService1 firstService = container.Resolve<IService1>();
IService2 secondService = container.Resolve<IService2>();

// This is false in that "InstancePerDependency" didn't result in a Captive Dependency
if (((Test.Service.Service1)firstService)._logger == ((Test.Service.Service2)secondService)._logger)
{
   throw new Exception("Loggers were the same");
}

What this means is that a captive dependency can only occur when you try register a class's lifetime to reside in the current scope it was resolved in or greater/parented scope.

What others have suggested is that just having your Singleton's dependencies be registered instance by dependency shouldn't make a big difference however, I think this is missing the whole point. There are cases where reusing the same dependency is important, I use the logger as a prime example where having multiple resolutions can lead to fighting over external resources like where data is being logged. What more, most of these captive dependency issues can be avoided simply by avoiding registering Singletons, as if your sharing within the child scopes you can at least close the child, but with singletons the dependency is captured in the root. And yet I would argue that these dependencies can be resolved as we desired without capture dependencies if we were to not use a IOC library like Autofac and pass them in manually ourselves, so logically it should be possible for AutoFac to do it if we can. However, I want Autofac to do the work for me and yet this is a huge trap to run into that I think most people should be aware of when they're looking at IOC libraries.

Question: I've read that updating Autofac to resolve this issue for shared registered components would be difficult be difficult to program, but is it really that hard for Autofac to know the singleton's dependencies that are registered to be shared across the scope should reside on said current scope only, and not the root scope where the Singleton will reside? I would venture that when a Singleton instance is resolved, the instance's dependencies do not need to reside in the root scope in order for the singleton instance to exist, as they will exist in memory due to their reference in the singleton instance, thus there's no reason for the root scope to save these dependencies.

If this is really the case, the only path seems to be to never use the Singleton functionality within AutoFac and to find another way to work around it. That said I think there should be a huge warning about this problem in the Singleton portion of AutoFac's documentation which is currently missing.

Lightbarrier
  • 361
  • 1
  • 4
  • 8
  • 2
    A singleton dependency only gets resolved once, which means its dependencies only get resolved once. That means regardless of how those nested dependencies are scoped, they get resolved once. To fix it, change the top-level dependencies to use a more transient scope. Another alternative (slightly messy) is to inject factories into the top-level dependencies so that their dependencies are re-resolved as needed. But using factories for lifetime management is a leaky abstraction. – Scott Hannen Aug 01 '19 at 23:32
  • I used the word "transient" poorly, although the distinction might not matter. I didn't mean literally "transient", but rather *more* transient - that is, anything other than singleton. If something is a singleton, its dependencies get injected once and it holds onto them. – Scott Hannen Aug 02 '19 at 00:05
  • This seems like a poor case though, what's the point of having Singleton reside in AutoFac unless the resolve singleton, never has any dependencies to resolve, or it's dependencies will never be used in another resolved object. At that point having Singleton functionality reside in AutoFac seems like more trouble then it's worth as you could just be one update away from an headache. – Lightbarrier Aug 02 '19 at 00:14
  • It's interesting that some frameworks have singleton as default, some transient, and then with Microsoft it's mostly explicit with a slight leaning towards transient. It's probably not worth it to manage the risk of singletons if it gets complicated. It's an optimization to create fewer class instances, but creating more instances usually isn't that big of a deal. – Scott Hannen Aug 02 '19 at 00:21
  • 1
    Just to add to the explanation, this scenario is documented here: https://autofaccn.readthedocs.io/en/latest/lifetime/captive-dependencies.html – John King Aug 02 '19 at 02:03
  • I've edited the original post to follow up on Captured dependencies, in which I discuss why it should be possible to avoid it while still using Singletons, and if not possible to avoid then the use of Singleton within AutoFac should be accompanied by a huge warning that's missing in the documentation. That said I would still argue that if I can write the code to manually pass these dependencies to my resolved object with the desired lifetime, it should be logically possible to code/configure AutoFac to do the job. Cases like these are arguments why not to use an IOC library. – Lightbarrier Aug 05 '19 at 20:58

0 Answers0