Phillip Ngan Phillip Ngan - 2 months ago 25
C# Question

Enumerating disposables tracked by an Autofac lifetime

Autofac uses lifetime scopes as a way of disposing of all of the components created during a unit of work. While this is a powerful feature, it is easy to write code that doesn't dispose the lifetime scopes properly, which results in the number of tracked disposables growing over time: effectively a memory leak.

Is there a way of monitoring the number of Disposable objects being tracked by a Lifetime Scope at any point in time. I'm interested in writing tool to help me find issues related to not properly assigning disposables to units of work. At the moment I use a memory profiler tool to find the leaks, but this is pretty onerous work.

I've looked at the public interface of ILifetimeScope but do not see anything that is of use.

Answer

Unfortunately, analytics is one of Autofac's weaker spots at the moment. There is a repository where some work was started on an analytics package but there are definitely some gaps - for example, you can track when an object is activated and you can see when lifetime scopes are disposed, but you can't track when individual objects are disposed as part of a scope. There's just no event for it.

A very simple tracking module for activations would look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Autofac.Core;

namespace DiagnosticDemo
{
  public class TrackingModule : Module
  {
    private readonly IDictionary<Type, int> _activations = new Dictionary<Type, int>();

    private readonly object _syncRoot = new object();

    public void WriteActivations()
    {
      foreach (var pair in this._activations.Where(p => p.Value > 0))
      {
        Console.WriteLine("* {0} = {1}", pair.Key, pair.Value);
      }
    }

    protected override void AttachToComponentRegistration(
      IComponentRegistry componentRegistry,
      IComponentRegistration registration)
    {
      if (registration.Ownership == InstanceOwnership.OwnedByLifetimeScope)
      {
        registration.Activated += this.OnRegistrationActivated;
      }
    }

    private void OnRegistrationActivated(
      object sender,
      ActivatedEventArgs<object> e)
    {
      if (e.Instance is IDisposable)
      {
        var type = e.Instance.GetType();
        Console.WriteLine("Activating {0}", type);
        lock (this._syncRoot)
        {
          if (this._activations.ContainsKey(type))
          {
            this._activations[type] = this._activations[type]++;
          }
          else
          {
            this._activations[type] = 1;
          }
        }
      }
    }
  }
}

You would use it something like this:

static void Main(string[] args)
{
  var trackingModule = new TrackingModule();

  var builder = new ContainerBuilder();
  // Register types, then register the module
  builder.RegisterType<Consumer>().As<IConsumer>();
  builder.RegisterType<DisposableDependency>().As<IDependency>();
  builder.RegisterType<NonDisposableDependency>().As<IDependency>();
  builder.RegisterModule(trackingModule);
  var container = builder.Build();

  // Do whatever it is you want to do...
  using (var scope = container.BeginLifetimeScope())
  {
    scope.Resolve<IConsumer>();
  }

  // Dump data
  Console.WriteLine("Activation totals:");
  trackingModule.WriteActivations();
}

However, this isn't going to tell you which items weren't disposed, which is I think what you want to know. It may get you some ideas, though, or at least help a bit.

If you're interested in helping to improve the analytics in Autofac, we'd love to take PRs or specific design ideas for how to improve.