Утечка памяти в Owin.AppBuilderExtensions

#c# #asp.net-identity #owin

#c# #asp.net-идентификация #owin

Вопрос:

Я использую OWIN Microsoft.AspNet.Идентификация.Owin (v.2.0.0.0) в веб-приложении. Я регистрирую UserManager / DbContext для каждого веб-запроса, как широко рекомендуется:

 app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
 

но ни один из них никогда не удаляется. Я взглянул на отражатель, и это похоже на ошибку в методе расширения:

 public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app, Func<IdentityFactoryOptions<T>, IOwinContext, T> createCallback) where T: class, IDisposable
{
    if (app == null)
    {
        throw new ArgumentNullException("app");
    }
    if (createCallback == null)
    {
        throw new ArgumentNullException("createCallback");
    }
    object[] args = new object[1];
    IdentityFactoryOptions<T> options = new IdentityFactoryOptions<T> {
        DataProtectionProvider = app.GetDataProtectionProvider()
    };
    IdentityFactoryProvider<T> provider = new IdentityFactoryProvider<T> {
        OnCreate = createCallback
    };
    options.Provider = provider;
    args[0] = options;
    app.Use(typeof(IdentityFactoryMiddleware<T, IdentityFactoryOptions<T>>), args);
    return app;
}
 

IdentityFactoryProvider имеет два обратных вызова — create и dispose, но обратный вызов dispose здесь не зарегистрирован. Я также подтвердил свои подозрения с помощью профилировщика памяти.

Я не вижу Owin на codeplex / github (на самом деле я думал, что это с открытым исходным кодом), поэтому я не знаю, где задать свой вопрос: может ли кто-нибудь еще подтвердить, что это утечка памяти? Я не совсем уверен, потому что Google ничего не говорит об этом, я ожидаю, что это должно обсуждаться везде, если это ошибка.

Комментарии:

1. Я взял на себя смелость переслать это команде Microsoft, которая обрабатывает этот код.

2. Было бы неплохо услышать их ответ, если таковой имеется.

3. Последнее, что я слышал, это расследование проблемы. Основная проблема заключается в том, что идентификационный код не является открытым исходным кодом, и довольно сложно создать запрос на извлечение, чтобы исправить это. На данный момент в codeplex есть заполнитель, но … пока ничего. Это может быть намеком на то, что в конечном итоге он будет с открытым исходным кодом.

4. Хотя наблюдение о том, что одноразовый объект для каждого запроса не удаляется, является ошибкой, отсутствие вызова Dispose не приводит к утечке памяти или, по крайней мере, не к утечке управляемой памяти. Dispose гарантирует, что неуправляемые ресурсы будут освобождены. Управляемая память освобождается сборщиком мусора, а не вызовом Dispose . Если управляемая память «протекает», то это потому, что инфраструктура OWIN постоянно хранит ссылки на каждую новую DbContext и UserManager запрещает сборщику мусора освобождать память при завершении запроса.

Ответ №1:

У меня также есть его проблема, ничто из того, что зарегистрировано в CreatePerOwinContext, не удаляется. Я использую версию v2.1.

Вот временное исправление, которое хорошо работает для меня в качестве обходного пути, пока эта библиотека не будет исправлена. По сути, вам нужно вручную зарегистрировать каждый из типов, которые используют register с CreatePerOwnContext в следующем классе, а затем в конце процедуры запуска вы регистрируете этот пользовательский класс:

 public sealed class OwinContextDisposal : IDisposable
{
    private readonly List<IDisposable> _disposables = new List<IDisposable>(); 

    public OwinContextDisposal(IOwinContext owinContext)
    {
        if (HttpContext.Current == null) return;

        //TODO: Add all owin context disposable types here
        _disposables.Add(owinContext.Get<MyObject1>());
        _disposables.Add(owinContext.Get<MyObject2>());

        HttpContext.Current.DisposeOnPipelineCompleted(this);
    }

    public void Dispose()
    {
        foreach (var disposable in _disposables)
        {
            disposable.Dispose();
        }
    }
}
 

В конце вашего процесса запуска зарегистрируйте этот класс:

  app.CreatePerOwinContext<OwinContextDisposal>(
      (o, c) => new OwinContextDisposal(c));
 

Теперь все будет удалено в конце конвейера запросов должным образом.

Ответ №2:

Утечка памяти в AppBuilderExtensions классе уже исправлена в последней версии Microsoft.AspNet.Identity.Owin библиотеки (2.2.1).

Я проверил код с помощью Reflector, а также путем установки точки останова в Dispose() методы объектов, созданных AppBuilderExtensions.CreatePerOwinContext() .

Ответ №3:

Вы можете поместить логику для удаления экземпляров, с которыми вы создаете, CreatePeOwinContext() в тот же обратный вызов, который вы используете для создания этих намерений. Это:

 public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.CreatePerOwinContext<ClassIWantOneInstancePerContext>(ClassIWantOneInstancePerContext.Create);

        //other code...
    }
}
 

Тогда вам нужно только позаботиться о том, чтобы включить вызов DisposeOnPipelineCompleted() в обратный вызов, используемый для создания экземпляра вашего класса. Это:

 public class ClassIWantOneInstancePerContext
{
     //other code...

    public static ClassIWantOneInstancePerContext Create()
    {
        ClassIWantOneInstancePerContext TheInstance = new ClassIWantOneInstancePerContext();
        HttpContext.Current.DisposeOnPipelineCompleted(TheInstance);

        return TheInstance;
    }
}
 

Также не забудьте включить Dispose() метод в определение вашего класса!

Комментарии:

1. Это может показаться странной причиной, но я хочу оставаться в рамках абстракции OWIN и не хочу ссылаться на System.Web…

2. Это приводит к зависимости от системы. Веб, но это определенно работает!

Ответ №4:

Использование : app.CreatePerRequest<AuthorizationContext>();

Метод расширения :

 public static IAppBuilder CreatePerRequest<T>(this IAppBuilder builder )where T:IDisposable
        {
            builder.Use(async (context, next) =>
            {
                var resolver = context.Get<IDependencyScope>();

                using (var instance = (T) resolver.GetService(typeof (T)))
                {
                    context.Set<T>(instance);
                    if (next != null)
                    {
                        await next();
                    }
                }

            });

            return builder;
        }
 

Чтобы использовать внедрение зависимостей, вы должны настроить owin: app.UseScopePerOwinRequest(_dependencyResolver); — Это должно быть первое промежуточное программное обеспечение..

 public static IAppBuilder UseScopePerOwinRequest(this IAppBuilder builder,IDependencyResolver resolver)
        {
            builder.Use(async (context, next) =>
            {
                using (var instance = resolver.BeginScope())
                {
                    context.Set<IDependencyScope>(instance);
                    if (next != null)
                    {
                        await next();
                    }
                }

            });

            return builder;
        }
 

И чтобы приведенный выше код работал, вы должны реализовать IDepedencyResolver с любым контейнером.

  • Запрос поступает, и создается новая область для запроса
  • В этой области вы создаете другой объект.
  • Используйте эти объекты в другом промежуточном программном обеспечении
  • и когда область действия заканчивается, она удаляется.
  • любые объекты в этой области, которые не удаляются, также удаляются.