Служба Windows с NHibernate увеличивает используемую память

Я отлаживаю существующую службу Windows (написанную на C#), которую необходимо перезапускать вручную каждые несколько месяцев, поскольку она продолжает потреблять память.

Услуга не очень сложная. Он запрашивает файл json с внешнего сервера, на котором хранятся продукты. Затем он анализирует этот файл JSON в список продуктов. Для каждого из этих продуктов проверяется, существует ли этот продукт в базе данных. Если нет, он будет добавлен, если он существует, свойства будут обновлены.

База данных представляет собой базу данных PostgreSQL, и мы используем NHibernate v3.2.0 в качестве ORM.

Я использовал JetBrains DotMemory для профилирования сервиса при его запуске: Обзоры DotMemory

Служба запускается и через 30 секунд начинает выполнять свою работу. Снэпшот №1 делается перед первым запуском. Снимок №6 сделан после 5-го прогона. Остальные снимки также сделаны после пробежки. Как видите, после каждого прогона количество объектов увеличивается примерно на 60 КБ, а используемая память увеличивается на несколько МБ после каждого запуска.

При ближайшем рассмотрении снимка № 6 видно, что сохраненный размер в основном используется объектами сеанса NHibernate:

Снимок № 6

Вот мой код OnStart:

try
{
    // Trying to fix certificate errors:
    ServicePointManager.ServerCertificateValidationCallback += delegate
    {
        _logger.Debug("Cert validation work around");
        return true;
    };

    _timer = new Timer(_interval)
    {
        AutoReset = false // makes it fire only once, restart when work is done to prevent multiple runs
    };
    _timer.Elapsed += DoServiceWork;
    _timer.Start();
}
catch (Exception ex)
{
    _logger.Error("Exception in OnStart: " + ex.Message, ex);
}

И мой DoServiceWork:

try
{
    // Call execute
    var processor = new SAPProductProcessor();
    processor.Execute();
}
catch (Exception ex)
{
    _logger.Error("Error in DoServiceWork", ex);
}
finally
{
    // Next round:
    _timer.Start();
}

В SAPProductProcessor я использую два вызова БД. Оба в петле. Я перебираю все продукты из файла JSON и проверяю, есть ли продукт уже в таблице, используя код продукта:

ProductDto dto;
using (var session = SessionFactory.OpenSession())
{
    using (var transaction = session.BeginTransaction(IsolationLevel.ReadCommitted))
    {
        var criteria = session.CreateCriteria<ProductDto>();
        criteria.Add(Restrictions.Eq("Code", code));
        dto = criteria.UniqueResult<ProductDto>();
        transaction.Commit();
    }
}
return dto;

И когда productDto обновляется, я сохраняю его, используя:

using (var session = SessionFactory.OpenSession())
{
    using (var transaction = session.BeginTransaction(IsolationLevel.ReadCommitted))
    {
        session.SaveOrUpdate(item);
        transaction.Commit();
    }
}

Я не уверен, как изменить приведенный выше код, чтобы остановить увеличение памяти и количество объектов.

Я уже пытался использовать var session = SessionFactory.GetCurrentSession(); вместо using (var session = SessionFactory.OpenSession()), но это не остановило увеличение памяти.

Обновить

В конструктор моего класса доступа к данным вводится MultiSessionFactoryProvider sessionFactoryProvider. И базовый класс вызывается с : base(sessionFactoryProvider.GetFactory("data")). Этот базовый класс имеет метод BeginSession:

ISession session = _sessionFactory.GetCurrentSession();
if (session == null)
{
  session = _sessionFactory.OpenSession();
  ThreadLocalSessionContext.Bind(session);
}

И EndSession:

ISession session = ThreadLocalSessionContext.Unbind(_sessionFactory);
if (session != null)
{
    session.Close();
}

В моем классе доступа к данным я вызываю base.BeginSession в начале и base.EndSession в конце.


person Paul Meems    schedule 15.11.2017    source источник
comment
ваш DoServiceWork синглтон? Я вижу, что SAPProductProcessor создается каждый раз. Может быть, есть на что посмотреть?   -  person Roelant M    schedule 15.11.2017
comment
SessionFactory - это свойство? Где создается SessionFactory? Его следует создавать только один раз для процесса (или AppDomain).   -  person Stefan Steinegger    schedule 15.11.2017
comment
Откройте сравнение, откройте все новые объекты и посмотрите, какие объекты удерживаются и почему.   -  person Ed Pavlov    schedule 15.11.2017
comment
Спасибо за ответы. Я попытаюсь сделать DoServiceWork синглтоном. SAPProductProcessor сначала был статическим классом. Я изменил его, чтобы сделать некоторую очистку в деструкторе, но это не помогло. Я обновил свой пост, добавив дополнительную информацию о SessionFactory.   -  person Paul Meems    schedule 15.11.2017


Ответы (1)


Предложение о синглтоне заставило меня поближе взглянуть на мой класс доступа к данным.

Я думал, что при создании этого класса при каждом запуске будет освобождаться память NHibernate, когда она выходит за рамки. Я даже добавил вызов dispose в деструктор класса. Но это не сработало, или, скорее, я не правильно это делаю. Теперь я сохраняю свой класс доступа к данным в статическом поле и повторно использую его. Теперь моя память больше не увеличивается, и, что более важно, количество открытых объектов остается прежним. Я просто снова запускаю службу, используя DotMemory, более часа, вызывая запуск около 150 раз, и память последнего снимка по-прежнему составляет около 105 МБ, а количество объектов по-прежнему составляет 117 КБ, а мой словарь SessionFactory теперь составляет всего 4 МБ вместо 150 * 4 МБ. .

person Paul Meems    schedule 17.11.2017