OutOfMemoryException от TaskScheduler в основном параллельном асинхронном приложении asp.net. - PullRequest
0 голосов
/ 10 июля 2019

В службе REST dotnet core 2.2, размещенной на AWS ECS FARGATE (докер), у меня регулярно (каждые 30-60 минут) происходит сбой экземпляра с System.OutOfMemoryException, хотя ECS сообщает о максимальном использовании памяти в 11% (из 16 ГБ). Авария всегда происходит из TaskScheduler (трассировка стека ниже). Это происходит только в производстве.

Мне нужен совет по устранению этой проблемы. (Изменить: я не считаю, что это на самом деле проблема OutOfMemory, если Thread:StartInternal() не сможет внезапно использовать 90% из 16 ГБ быстрее, чем инструменты мониторинга AWS могут зарегистрировать его)

Приложение работает локально, в Windows 10, и я также пытался воспроизвести на отдельном кластере ECS (нашем тестовом кластере), поддерживая 100 одновременных запросов, но безуспешно. Одна конечная точка сервиса получает 99% + запросов. Основная операция:

  • Попробуйте найти некоторые документы в базе данных MongoDB (на основе входных данных), используя async/await
  • Извлечение данных из WCF (синхронизация, см. Ниже)
  • Для некоторых результатов извлекайте данные с внешнего URL (иногда медленно), используя System.New.WebRequest, используя async/await
  • Результаты поиска

Служба WCF называется синхронизацией, поскольку мы используем клиентскую библиотеку поверх WCF, которая не является асинхронной. Тем не менее, результат сохраняется в MemoryCache в течение 1 минуты, а повторная выборка по истечении срока действия защищена с помощью AsyncEx.AsyncMonitor , поэтому только один вызывающий пользователь может обновлять кэш, например:

using( await _monitor.EnterAsync( ) )
{
    if( !Cache.TryGetValue( "UserLookup", out LookupUsers lookupUsers ) )
    {
        lookupUsers = await GetCachedUsers( ssoToken );
        Cache.Set( "UserLookup", lookupUsers, TimeSpan.FromMinutes( 1 ) );
    }
    return lookupUsers;
}

GetCachedUsers() делает это:

var users = await Task.Run( ( ) => client.Proxy.ListUsers( new ListUsersInput { } ) );

А также возвращает значение по умолчанию в случае тайм-аута или другой проблемы.

Точка входа в действие:

[Route( "get-content" )]
[HttpPost]
public async Task<RemoteGetContentResult> GetContent( [FromBody]RemoteGetContentInput input )
{
    // input validation
    var c = Interlocked.Increment( ref _concurrency );
    try
    {
        // log value of _concurrency
        return await _provider.GetContentExAsync( input );
    }
    finally
    {
        Interlocked.Decrement( ref _concurrency );
    }
}

Уровень регистрируемого уровня параллелизма обычно составляет 10-30, но может достигать 100 (при наличии множества внешних http-выборок).

Вот трассировка стека, которую я вижу в журналах AWS ECS:

2019-07-10T06:22:39.554Z Unhandled Exception: System.Threading.Tasks.TaskSchedulerException: An exception was thrown by a TaskScheduler. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
2019-07-10T06:22:39.554Z    at System.Threading.Thread.StartInternal()
2019-07-10T06:22:39.554Z    at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
2019-07-10T06:22:39.554Z    --- End of inner exception stack trace ---
2019-07-10T06:22:39.554Z    at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
2019-07-10T06:22:39.554Z    at System.Threading.Tasks.Task.InternalStartNew(Task creatingTask, Delegate action, Object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions)
2019-07-10T06:22:39.554Z    at System.Runtime.IOThreadScheduler.ScheduleCallbackHelper(SendOrPostCallback callback, Object state)
2019-07-10T06:22:39.554Z    at System.Runtime.IOThreadScheduler.ScheduleCallbackNoFlow(SendOrPostCallback callback, Object state)
2019-07-10T06:22:39.554Z    at System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.System.Runtime.CompilerServices.IStateMachineBoxAwareAwaiter.AwaitUnsafeOnCompleted(IAsyncStateMachineBox box)
2019-07-10T06:22:39.554Z    at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AwaitUnsafeOnCompleted[TAwaiter,TStateMachine](TAwaiter& awaiter, TStateMachine& stateMachine)
2019-07-10T06:22:39.554Z --- End of stack trace from previous location where exception was thrown ---
2019-07-10T06:22:39.554Z    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
2019-07-10T06:22:39.554Z --- End of stack trace from previous location where exception was thrown ---
2019-07-10T06:22:39.554Z    at System.Threading.ThreadPoolWorkQueue.Dispatch()

UPDATE: Каждые 5 секунд я добавляю дополнительные записи о процессе. В 18:30: 16.741Z было зарегистрировано:

2019-07-10T18:30:16.741Z concurrency:   4 proc thread cnt:   29 avail worker threads: 32,766 avail compl port threads:  1,000 ws: 1,733,996,544 peak ws:      0

Итак, рабочий набор ~ 1,7 ГБ из 16 ГБ. (По какой-то причине Peak WS всегда равен 0, но максимум, который я видел, составляет 2 053 316 608 байт). Через 4 секунды выдается исключение OOM:

2019-07-10T18:30:20.630Z Unhandled Exception: System.Threading.Tasks.TaskSchedulerException: An exception was thrown by a TaskScheduler. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.

1 Ответ

0 голосов
/ 18 июля 2019

Оказалось, что мы использовали библиотеку, которая использовала HttpClient, не выбрасывая ее, таким образом, протекали сокеты.

Мы некоторое время использовали эту библиотеку в Windows, но, очевидно, сокеты в конечном итоге закрываются финализатором, но не в Linux.

Я наконец запустил приложение на обычном компьютере с Linux, чтобы упростить мониторинг ОС. Оказывается, эта команда

$ lsof -p <PID>

вернул тысячи таких строк

dotnet  15613 ec2-user  215u     sock                0,8      0t0  4968805 protocol: TCP
dotnet  15613 ec2-user  219u     sock                0,8      0t0  4968844 protocol: TCP
dotnet  15613 ec2-user  220u     sock                0,8      0t0  4968236 protocol: TCP
dotnet  15613 ec2-user  221u     sock                0,8      0t0  4968247 protocol: TCP
...

Преобразование использования HttpClient в singleton решило проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...