2

I am profiling a webapi application that is slowing down heavily under load (100's of requests / sec)

The application makes calls to a third-party WCF service.

I'm using dottrace, and notice that Autofac.Integration.Wcf.RegistrationExtensions.CloseChannel(T) is called > 22k times during my tests (100 clients / sec for 1 minute)

DotTrace has identified this as a hotspot.

I register my WCF as per the Autofac WCF integration documentation:

var bookingManagerFactory = new ChannelFactory<IBookingManager>("IBookingManager");

builder.Register(c => bookingManagerFactory).SingleInstance();

builder.Register(c => c.Resolve<ChannelFactory<IBookingManager>>().CreateChannel()).UseWcfSafeRelease();

Is this behaviour expected?

Across the app, there are many classes which depend on IBookingManager
It's my understanding that InstancePerDependency is the default lifetime - is that suitable in my scenario? Would .InstancePerRequest() be more applicable (on the CreateChannel() method?)

Alex
  • 37,502
  • 51
  • 204
  • 332

1 Answers1

3

The short answer: it's expected, and I don't think it's a problem.

A bit longer dive into it:

Autofac holds onto disposable objects until the lifetime scope is disposed, at which point anything disposable is disposed. (You can manually dispose of objects if you register them as Owned, but in the case of WCF stuff I'd leave it.)

WCF channels are a bit of a notorious challenge because they're disposable, but the IDisposable implementation will throw an exception if the channel is faulted (e.g., if you got an error response), which is actually very bad disposal design. Anyway, that's what UseWcfSafeRelease handles - it makes sure your channels get properly disposed by correctly closing and/or aborting the channel based on its state. CloseChannel is just the method that handles the proper cleanup.

If you're resolving a bunch of channels, those are all going to have to get cleaned up somehow or another. If you see that CloseChannel getting called 22,000 times, it means you resolved 22,000 instances that all needed to be safely cleaned up.

I don't know that I'd change the lifetime scope. Another WCF problem is that if you fault the channel then you can't reuse (or reopen) the client proxy. You're done, and you need a new channel.

A better option to optimize your solution would be to resolve Func<IBookingManager> in your constructor. Whenever you need a proxy, call the function to get a new one.

public class MyConsumer
{
  private Func<IBookingManager> _factory;
  public MyConsumer(Func<IBookingManager> factory)
  {
    this._factory = factory;
  }
  public void DoWork()
  {
    this._factory().CallOperation(input);
  }
}

That provides two benefits:

  1. If you have methods on the class that don't need a channel resolved, you can save the creation of the channel (and the disposal).
  2. If you handle errors (e.g., timeout/retry), you can create new proxies when old ones are faulted and your class can be more fault tolerant.

Outside of that, I don't think I'd worry about trying to avoid that CloseChannel call. If you need to optimize, optimize the number of channels you create.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • Would passing in a Lazy be the same? – Alex May 21 '14 at 22:09
  • 1
    Lazy delays resolution but only for one instance. If you only ever need one and only ever make one call with it (or don't need fault tolerance) then Lazy is fine. However, it's just as easy to use Func and it gives you way more flexibility. I'd highly recommend the Func over Lazy for your situation. – Travis Illig May 22 '14 at 02:30
  • When I pass a Func in, should I use BeginInvoke? Invoke? – Alex May 22 '14 at 12:57
  • Neither. The example code in my answer shows exactly what to do - just call it like a function. – Travis Illig May 22 '14 at 13:39
  • Ah yeah, I missed the () - Thanks, will try that. I changed the registration to use .InstancePerLifetimeScope() (on CreateChannel) - is that going to cause issues? – Alex May 22 '14 at 15:13
  • Yes it will be a problem. The function will always return only the one instance for each lifetime scope (request), so if you fault the channel, you're hosed. Leave it `InstancePerDependency` and let the `Func` do the work of figuring out when to create one. (Also, if you like this answer, please mark it as the answer.) – Travis Illig May 22 '14 at 19:18