В службе 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.