непредвиденная утечка памяти из-за циклической ссылки на обратный вызов - PullRequest
7 голосов
/ 16 февраля 2012

Сегодня у нас произошел серьезный сбой в работе, когда память очень быстро исчезала из наших веб-серверов. Это было прослежено до механизма кэширования в Ninject (я думаю, что это был Activation Cache или что-то - не совсем уверен). Изучив проблему, мы пришли к выводу, что в обратном вызове области действия была циклическая ссылка.

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 и кэшировать их? Я предполагаю, что были бы другие проблемы, связанные с возвращением неправильного объекта, но это, по крайней мере, просто ошибка, а не утечка памяти.

Мой вопрос: а) правильно ли мы диагностировали эту ошибку? б) есть ли рекомендуемый подход, чтобы это не повторилось? c) Могу ли я внести исправление в код для проверки этого типа циклической зависимости (при условии, что мы правильно поставили диагноз)?

1 Ответ

7 голосов
/ 16 февраля 2012

Проще говоря, кеш - это словарь объекта со слабой ссылкой на экземпляр.Пока область действия жива, ссылочные объекты также остаются живыми.Поэтому да, если ваш CookieContainer ссылается на HttpContext.Current и находится в области запроса, этот стандартный механизм никогда не будет применяться для их освобождения.

Но в особом случае InRequestScope существует другой механизм освобождения, реализованный OnePerRequestModule, который освободит всеInRequestScoped объект сразу после завершения запроса.Если вы используете актуальную версию Ninject.Web или Ninject.Web.MVC3, то она предварительно настроена.В противном случае вы должны добавить его явно, настроив этот HTTPModule в web.config.

Другой момент, который вы не поняли, заключается в том, что Ninject не будет возвращать один и тот же объект, пока он живет.Например, в объеме запроса он вернет тот же объект для запроса.Если несколько запросов выполняются одновременно, все они получают разные экземпляры.

...