Search code examples
dependency-injectionninjectninject-interception

Ninject interception in multithreaded environment


I'm trying to create an interceptor using Ninject.Extensions.Interception.DynamixProxy to log method completion times.

In a single threaded environment something like this works:

public class TimingInterceptor : SimpleInterceptor
{
    readonly Stopwatch _stopwatch = new Stopwatch();
    private bool _isStarted;


    protected override void BeforeInvoke(IInvocation invocation)
    {
        _stopwatch.Restart();
        if (_isStarted) throw new Exception("resetting stopwatch for another invocation => false results");
        _isStarted = true;
        invocation.Proceed();
    }

    protected override void AfterInvoke(IInvocation invocation)
    {
        Debug.WriteLine(_stopwatch.Elapsed);
        _isStarted = false;
    }
}

In multithreaded scenarios this would however not work because the StopWatch is shared between invocations. How to pass an instance of StopWatch from BeforeInvoke to AfterInvoke so it would not be shared between invocations?


Solution

  • This should work just fine in a multi-threaded application, because each thread should get its own object graph. So when you start processing some task, you start with resolving a new graph and graphs should not be passed from thread to thread. This allows keeping the knowledge of what is thread-safe (and what not) centralized to the one single place in the application that wires everything up: the composition root.

    When you work like this, it means that when you use this interceptor to monitor classes that are singletons (and used across threads), each thread will still get its own interceptor (when its registered as transient), because every time you resolve you get a new interceptor (even though you reuse the same 'intercepted' instance).

    This however does mean that you have to be very careful where you inject this intercepted component into, because if you inject this intercepted object into another singleton, you will be in trouble again. This particular sort of 'trouble' is called captive dependency a.k.a lifestyle mismatch. It's really easy to accidentally misconfigure your container to get yourself into trouble by this, and unfortunately Ninject lacks the possibility to warn you about this.

    Do note though, that your problems will disappear in case you start using decorators, instead of interceptors, because with a decorator you can keep everything in a single method. This means that even the decorator can be a singleton, without causing any threading issues. Example:

    // Timing cross-cutting concern for command handlers
    public class TimingCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
    {
        private readonly ICommandHandler<TCommand> decoratee;
    
        public TimingCommandHandlerDecorator(ICommandHandler<TCommand> decoratee)
        {
            this.decoratee = decoratee;
        }
    
        public void Handle(TCommand command)
        {
            var stopwatch = Stopwatch.StartNew();
            this.decoratee.Handle(command);
            Debug.WriteLine(stopwatch.Elapsed);
        }
    }
    

    Of course, the use of decorators is often only possible when you correctly applied the SOLID principles to your design, because you often need to have some clear generic abstractions to be able to apply decorators to a large range of classes in your system. I can be daunting to use decorators efficiently in a legacy code base.