Исключение из-за нехватки памяти возникает при постановке в очередь и обработке фоновых заданий
Я могу вызвать исключение воспроизводимой нехватки памяти при постановке в очередь и обработке фоновых заданий с помощью Hangfire.
Работа проста Console.WriteLine
вызовы, поэтому я не ожидал бы, что память кучи увеличит то, как это происходит.
Я неправильно настроил или мне нужно подумать о регистрации проблемы?
Результаты ( VMMap)
Использование Redis в качестве резервного хранилища для заданий:
- В начале общая куча = 29,088K;
- после 5000 рабочих мест - 938 672 тыс.
- 6000 рабочих мест, 1 056 004 000;
- 7000 рабочих мест, 1 219 296 000;
- 8000 заданий, кучи значения нет;
- в течение 100 рабочих мест,
iisexpress.exe
Экземпляр падает.
С хранилищем SQL предел намного выше ~ 15 000 заданий.
Настроить
- пустой проект ASP.NET;
- установить пакеты Owin для хостинга IIS и Hangfire;
- класс запуска и контроллер.
пакеты
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Hangfire.Core" version="1.6.6" targetFramework="net452" />
<package id="Hangfire.Pro" version="1.4.7" targetFramework="net452" />
<package id="Hangfire.Pro.PerformanceCounters" version="1.4.7" targetFramework="net452" />
<package id="Hangfire.Pro.Redis" version="2.0.2" targetFramework="net452" />
<package id="Hangfire.SqlServer" version="1.6.6" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.3" targetFramework="net452" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.0" targetFramework="net452" />
<package id="Microsoft.Net.Compilers" version="1.0.0" targetFramework="net452" developmentDependency="true" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net452" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="Owin" version="1.0" targetFramework="net452" />
<package id="StackExchange.Redis" version="1.1.606" targetFramework="net452" />
</packages>
контроллер
public class DefaultController : ApiController
{
static int _;
[HttpPost]
public void Post(int count = 1000)
{
for (var i = 0; i < count; ++i)
{
BackgroundJob.Enqueue(() => Console.WriteLine(_));
++_;
}
}
}
Запускать
static class AppSettings
{
internal static bool HangfireUseRedis => true;
internal static int RedisDatabase => 0;
internal static string RedisConnection => "localhost:6379";
internal static string SqlConnection => "Data Source=(localdb)\\v11.0;Initial Catalog=Hangfire";
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
if (AppSettings.HangfireUseRedis)
{
var redisOptions = new RedisStorageOptions
{
Database = AppSettings.RedisDatabase,
Prefix = "Foobar:"
};
GlobalConfiguration.Configuration.UseRedisStorage(AppSettings.RedisConnection, redisOptions);
}
else
{
GlobalConfiguration.Configuration.UseSqlServerStorage(AppSettings.SqlConnection);
}
JobHelper.SetSerializerSettings(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
app.UseHangfireServer();
app.UseHangfireDashboard();
app.UseWebApi(config);
}
}
2 ответа
После получения файла мини-дампа (1,2 ГБ) я смог получить информацию о кучах вашего процесса. Большинство из них не содержат ничего интересного, и их размер относительно невелик, вот выдержка из наиболее важных понятий:
GC Heap Size: Size: 0x9f7eb8 (10452664) bytes.
Jit code heap: Size: 0x1000 (4096) bytes total, 0x905a4d00 (2421837056) bytes wasted.
Как мы видим, размер кучи GC составляет около 10 МБ, поэтому в самом коде.NET нет утечек, поскольку его размер относительно невелик. Но куча кода Jit выглядит очень странно, поэтому я решил посмотреть, какие модули используются процессом, и нашел один из Stackify Profiler:
6b0d0000 6b23a000 StackifyProfiler_x86 (deferred)
PEB показывает переменную среды StackifyIsPrefix=1
это говорит нам, что используется префикс Stackify. Профилировщики могут изменить код JIT для инструментальных куколок, поэтому я решил установить префикс Stackify, чтобы попытаться воспроизвести проблему.
Я создал простое приложение MVC, изменил Home/Index
действие для постановки в очередь 10000 фоновых заданий и включения профилировщика. Сделав этот шаг, я обнаружил, что получение этой страницы занимает слишком много времени - 1,5 минуты, и профилировщик не показывает никаких данных. Это было слишком долго. Поэтому я решил сравнить синхронизацию с выключенным профилировщиком - это заняло всего 5 секунд. Это огромная разница, но я не смог воспроизвести проблемы с памятью.
Я уменьшил количество заданий до 100, включил профилировщик и понял, что каждый вызов в Redis считается, есть сотни записей для вызовов в Redis. Хранение их всех может привести к проблемам с памятью, но я не знаю точно, как они хранятся в префиксе Stackify.
Я не смог воспроизвести исходную проблему с памятью. Однако префикс Stackify существенно влияет на выполнение, увеличивая его продолжительность. Вы пытались отключить профилировщик префиксов Stackify и повторно запустить свои тесты? Обновленная версия также может исправить проблему с памятью.
Я могу согласиться с приведенным выше комментарием от odinserj, потому что я написал профилировщик префиксов.
Мы внесли некоторые изменения в дизайн, чтобы учесть фоновые потоки, которые работают в таких библиотеках, как Hangfire. Проблема в том, что мы сохраняем теневые стеки в памяти для каждого потока - в обычном веб-приложении мы очищаем этот стек, когда запрос заканчивается. Но потоки, которые раскручивает Hangfire, будут существовать на протяжении всего жизненного цикла домена приложения.
Вы обнаружите, что в последней версии влияние должно быть намного меньше, так как мы учли некоторые конкретные методы зависания, и затем мы выпустим часть этого стека теней.