We had a major outage today in production where memory was disappearing very quickly from our webservers. This was traced back to a caching mechanism in Ninject (I think it was Activation Cache or something - not totally sure). After investigating the issue we came to the conclusion that we had a circular reference in our scope callback.
class View
{
Presenter presenter;
View()
{
//service locators are smelly, but webforms forces this uglyness
this.presenter = ServiceLocator.Get<Func<View, Presenter>>()(this);
this.presenter.InitUI();
}
}
class Presenter
{
CookieContainer cookieContainer;
View view;
Presenter(View view, CookieContainer cookieContainer)
{
this.view = view;
this.cookieContainer = cookieContainer;
}
}
class CookieContainer
{
HttpRequest request;
HttpResponse response;
CookieContainer()
{
this.request = HttpRequest.Current.Request;
this.response = HttpRequest.Current.Response;
}
}
Bind<Func<View, Presenter>>().ToMethod(ctx => view =>
ctx.Kernel.Get<Presenter>(new ConstructorArgument("view", view)));
Bind<Presenter>().ToSelf().InTransientScope();
Bind<CookieContainer>().ToSelf().InRequestScope();
This is a representation of our code that was causing the issue. Seemingly what happened was the scope callback for the CookieContainer was HttpContext.Current, and HttpContext.Current was also being referenced by CookieContainer. So Ninject could never prune CookieContainer instances from its cache, as the CookieContainer instances where keeping their scope callback objects alive. When we change CookieContainer's scope to transient, all works fine, as we expected. But I'm still not totally sure why this happened as it seems like this is a fairly conventional thing to do right? Maybe not perhaps...
I am also confused as I thought that if the callback object stayed alive as it did, then shouldn't Ninject just hand back the same instance from the cache, seeing as though the callback was still alive, so the instance should appear to be in scope? Why would ninject keep getting new instances of CookieContainer and caching them? I guess there would be other issues related to the incorrect object coming back, but that would at least just be a bug, not a memory leak.
My question is a) have we diagnosed this error correctly? b) is there a recommended approach to take for this not to happen again? c) Can I put a fix in to the code to check for this type of circular dependancy (assuming we have diagnosed this correctly)?