CryptoPro / libcore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MemLeak в приложении с КриптоПро

k0st1x opened this issue · comments

Есть микросервис с КриптоПро под Debinan.
Со временем контейнер перезагружается из-за лимита памяти.
По MemoryDump видно, что он занимает ~4Gb, причем .net managed памяти там съедено ~300Mb, которые не текут.
В коде приложения загружаем сертификат с паролем, он висит всё время в памяти, им только подписываем документ и проверяем отсоединенные подписи присланных документов.

Код, который использует КриптоПро

// старт приложения (один раз при запуске)
LibCore.Initializer.Initialize();
X509Certificate2 certificate = X509CertificateExtensions.Create(data, password, CpX509KeyStorageFlags.CspNoPersistKeySet);

// метод подписи
var contentInfo = new ContentInfo(data);
var cms = new SignedCms(contentInfo, true);
var signer = new CmsSigner(certificate)
{
  IncludeOption = X509IncludeOption.EndCertOnly
};
cms.ComputeSignature(signer, silent: true);
var sign = cms.Encode();

использую пакет LibCore.Linux.2023.4.25.1.nupkg
версия КриптоПро 5.0.12600

Сервис приходится перезагружать раз в несколько дней - это критично для нас.
Нужна консультация/идеи, как разобраться с проблемой.

Похоже на то, с чем столкнулись мы в #40.
Вне контейнера проблема с большим потреблением сохраняется? Какой у вас режим сборщика мусора? Запускали ли профилировщик, какие результаты?

Вне контейнера проблема с большим потреблением сохраняется?

специально не проверял, но интересует сценарий именно в контейнере.

Какой у вас режим сборщика мусора?

Server

Запускали ли профилировщик, какие результаты?

Запускали, по результатам нет интересного, за что можно зацепиться.
Хотя память понемногу, но всё время растёт в unmanaged части.

К сожалению, сейчас попали в ситуацию, что криптопро sdk для .net на проде не помогает решать задачу, а является слишком нестабильным.

А что за проблемы с КриптоПро .NET?
Мы стараемся пока его поддерживать, но после серьёзных обновлений .NET Framework патчи слетают к сожалению -- нужно дожидаться новой версии от нес -- эти деятели постоянно меняют компилятор.
Но на проде же можно не обновлять Framework сразу.

  1. Попробуйте заменить режим сборки мусора на WorkStation и понаблюдать
  2. Произведите профилирование неуправляемого кода с помощью dotnet-dump по инструкции

А что за проблемы с КриптоПро .NET?

Используем net6, как и написано в документации.

  1. Попробуйте заменить режим сборки мусора на WorkStation и понаблюдать

пробовали чистить память GC.Collect в ручном режиме, после запроса на подпись. Не помогло - память накапливается.
Т.е. не считаем, что смена стратегии GC сможет помочь.

Произведите профилирование неуправляемого кода с помощью dotnet-dump по инструкции

по инструкции же только про анализ managed части памяти. в unmanaged можно только посмотреть, какое её количество. либо покажите, пожалуйста, что именно вы имеете ввиду. Но я попозже попробую собрать именно dotnet-dump и посмотрю на него повнимательнее.

  1. Попробуйте заменить режим сборки мусора на WorkStation и понаблюдать

пробовали чистить память GC.Collect в ручном режиме, после запроса на подпись. Не помогло - память накапливается. Т.е. не считаем, что смена стратегии GC сможет помочь.

Произведите профилирование неуправляемого кода с помощью dotnet-dump по инструкции

по инструкции же только про анализ managed части памяти. в unmanaged можно только посмотреть, какое её количество. либо покажите, пожалуйста, что именно вы имеете ввиду. Но я попозже попробую собрать именно dotnet-dump и посмотрю на него повнимательнее.

Действительно, dotnet-dump позволяет получить информацию об управляемой памяти, лучше использовать lldb и sos для анализа. Дамп можно сделать с помощью того же dotnet-dump

Upd: дамп лучше делать не с помощью dotnet-dump (т.к. он не нативный отладчик), а с помощью gcore. Вообще, дамп у вас уже должен быть, т.к. в большинстве дистрибутивов включено автоматическое создание дампа при падении.

https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/lab-1-2-analyze-core-dumps-lldb-debugger

Вообще, Workstation GC - это не просто вызвать GC.Collect

Про сравнение 2х дампов памяти (dotnet-dump)
image
это разница 2х дампов - первый: прогретый, вторй: +200 запросов на detached-подписи документов.
Видно, что сертификаты прогретые - CertificatePal - висят, не меняются.
А вот некий SafeCertContextHandle растёт с количеством запросов - вырос с 638 до 1271.

Так же отмечу, что managed памяти примерно одинаково - 300Mb,
а сами дампы - 2Gb и 2.5Gb,
т.е. вижу, что unmanaged памяти там съедено много.

Вообще, Workstation GC - это не просто вызвать GC.Collect

Я лишь хотел показать, что пробовал разные сценарии. И чистку LOH тоже применял. Но результат один - контейнеру нехватает памяти и происходит падение.
Т.е. вижу, что игры с GC не помогут, тк где-то нативную память надо было почистить, а она растёт.

PS. с gcore разобраться пока не успел. но не уверен, что без криптопро pdb мне это поможет как-то разобраться в native памяти.

ответ по SafeCertContextHandle в #40.
Не факт, что вам вообще удастся что-то найти в неуправляемой памяти. Исследование неуправляемой памяти в Linux - довольно непростая задача.

Посмотрел немного CoreFX, SafeHandle (от которого наследуется SafeCertContextHandle) имеет деструктор, а значит даже если в самом CoreFX не был вызван Dispose, то GC все равно соберет мусор, но только позже. Поэтому проблема с утечкой в неуправляемой памяти не должна быть связана с этим.

@k0st1x, как проблему в итоге решили?

@Bykiev, отказ от LibCore.Linux nupkg.
И ждем релиза версии под net8 в надежде, что там не будет проблемы.

@Bykiev, отказ от LibCore.Linux nupkg. И ждем релиза версии под net8 в надежде, что там не будет проблемы.

А вариант с заданием максимальной памяти в юните с автоперезапуском сервиса не устроил?

@Bykiev,
А вариант с заданием максимальной памяти в юните с автоперезапуском сервиса не устроил?

Если это про перезагрузку контейнера при достижении лимита по памяти, то с этим имею плохой опыт - при нагрузке есть вероятность отшибить запрос, который только пришел, но сервису пора перезапускаться. Конечно же, от сервиса работы с криптографией ожидаем максимально стабильной работы без перебоев и mem leak'ов.

нет, сервис ведь запускается systemd?

@Bykiev

нет, сервис ведь запускается systemd?

по инструкции от MS https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-6.0#build-and-deploy-manually

из командной строки обычное

dotnet MyApp.dll

тогда не получится так

@Bykiev,
А вариант с заданием максимальной памяти в юните с автоперезапуском сервиса не устроил?

Если это про перезагрузку контейнера при достижении лимита по памяти, то с этим имею плохой опыт - при нагрузке есть вероятность отшибить запрос, который только пришел, но сервису пора перезапускаться. Конечно же, от сервиса работы с криптографией ожидаем максимально стабильной работы без перебоев и mem leak'ов.

Мы тоже делали сервис на net6 для подписания документов. Правда на винде.
А вы не пробовали такой финт ушами - вынести подписание в консольное приложение, которое уже и вызывать из сервиса. По идее, при завершении консольки, все ресурсы за ней должны почиститься системой, даже если утечки были.
Слишком медленно для вас ?

@Bykiev,
А вариант с заданием максимальной памяти в юните с автоперезапуском сервиса не устроил?

Если это про перезагрузку контейнера при достижении лимита по памяти, то с этим имею плохой опыт - при нагрузке есть вероятность отшибить запрос, который только пришел, но сервису пора перезапускаться. Конечно же, от сервиса работы с криптографией ожидаем максимально стабильной работы без перебоев и mem leak'ов.

Мы тоже делали сервис на net6 для подписания документов. Правда на винде. А вы не пробовали такой финт ушами - вынести подписание в консольное приложение, которое уже и вызывать из сервиса. По идее, при завершении консольки, все ресурсы за ней должны почиститься системой, даже если утечки были. Слишком медленно для вас ?

в итоге примерно так и делаем. только не своё приложение, а используем стандартный certmgr.
из-за чего имеем неудобнфый код с динамическим обновлением сертификатов в контейнере.
и это не отменяет того, что у платного продукта есть критическая проблема в реализации библиотеки.

@k0st1x, а у вас случайно не установлен антивирус?

@k0st1x, а у вас случайно не установлен антивирус?

Софт крутится внутри docker. Внутри докер-образа антивируса нет.