由于循环作用域回调引用而导致 ninject 内存泄漏

发布于 2025-01-06 12:35:26 字数 1676 浏览 1 评论 0原文

今天我们的生产发生了一次严重的中断,我们的网络服务器的内存很快就消失了。这可以追溯到 Ninject 中的缓存机制(我认为它是激活缓存或其他东西 - 不完全确定)。在调查该问题后,我们得出的结论是,我们的范围回调中存在循环引用。

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();

这是导致问题的代码的表示。看起来发生的情况是 CookieContainer 的范围回调是 HttpContext.Current,并且 CookieContainer 也引用了 HttpContext.Current。因此,Ninject 永远无法从其缓存中删除 CookieContainer 实例,因为 CookieContainer 实例会保持其范围回调对象处于活动状态。当我们将 CookieContainer 的范围更改为瞬态时,一切正常,正如我们所期望的那样。但我仍然不完全确定为什么会发生这种情况,因为这似乎是一个相当传统的事情,对吧?也许不是……

我也很困惑,因为我认为如果回调对象像以前那样保持活动状态,那么 Ninject 不应该从缓存中交回相同的实例,就像回调仍然活动一样,所以实例应该出现在范围内?为什么 ninject 会不断获取 CookieContainer 的新实例并缓存它们?我想还会有与返回的错误对象相关的其他问题,但这至少只是一个错误,而不是内存泄漏。

我的问题是 a) 我们是否正确诊断了这个错误? b) 是否有推荐的方法可以避免这种情况再次发生? c) 我可以在代码中进行修复来检查这种类型的循环依赖(假设我们已经正确诊断了这一点)吗?

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)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

山川志 2025-01-13 12:35:26

简单描述一下,缓存就是实例的弱引用作用域对象的字典。只要范围还活着,引用的对象也保持活着。因此,是的,如果您的 CookieContainer 引用 HttpContext.Current 并且位于请求范围内,则此标准机制将永远不会适用于释放它们。

但在 InRequestScope 的特殊情况下,OnePerRequestModule 实现了另一种释放机制,该机制将在请求完成后立即释放所有 InRequestScoped 对象。如果您使用的是 Ninject.Web 或 Ninject.Web.MVC3 的最新版本,那么它是预先配置的。否则,您必须通过在 web.config 中配置此 HTTPModule 来显式添加它。

您不明白的另一点是,只要对象存在,Ninject 就不会返回相同的对象。例如,在请求范围内,它将为请求返回相同的对象。如果多个请求同时运行,它们都会获得不同的实例。

Simply described, the cache is a dictionary of the weak referenced scope object to the instance. As long as the scope is alive the referenced objects are kept alive too. So yes if your CookieContainer referneces HttpContext.Current and is in request scope this standard mechanism will never apply to release them.

But in the special case of InRequestScope there is another releasing machanism implemented by the OnePerRequestModule which will release all InRequestScoped object immediately after the request is complete. If you are using an up to date version of either Ninject.Web or Ninject.Web.MVC3 then it is preconfigured. Otherwise you have to add it explicitly by configuring this HTTPModule in the web.config.

The other point you didn't understand is that Ninject will not return the same object as long as it lives. E.g. in request scope it will return the same object for a request. If multiple requests run at the same time they all get a different instance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文