Aspnetcore: BadHttpRequestException: время чтения тела запроса истекло из-за слишком медленного поступления данных

Созданный на 16 окт. 2018  ·  129Комментарии  ·  Источник: dotnet/aspnetcore

У нас есть приложение asp.net core 2.1, работающее в .net 4.7 в службе веб-приложений Azure.

Недавно мы начали получать МНОГО следующих ошибок:

Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: время чтения тела запроса истекло из-за слишком медленного поступления данных. См. MinRequestBodyDataRate.

Наше приложение постоянно находится под большой нагрузкой (в основном из-за входящих веб-перехватчиков). Во время больших всплесков мы, как правило, видим их больше.

Раньше мы работали на asp.net core 2.0 / .net 4.6.1 в течение многих месяцев и никогда раньше не видели этой ошибки.
Похоже, это началось после нашего недавнего обновления до asp.net core 2.1 / .net 4.7.

Мы хотели бы разобраться в этой проблеме, но мы даже не уверены, с чего начать.
Есть предположения?

affected-medium area-servers enhancement servers-kestrel severity-nice-to-have

Самый полезный комментарий

Приятно слышать, что некоторые находят решения.
Лично я не люблю применять обходные пути, которые я действительно не понимаю, без предварительного понимания проблемы.

Насколько я понимаю, при большой нагрузке ошибка «Время ожидания чтения тела запроса истекло из-за слишком медленного поступления данных» не является реальной причиной. Это следствие нагрузки на систему.
Я до сих пор не понял, ПОЧЕМУ система испытывает стресс, учитывая, что показатели выглядят нормально (ЦП, память и потоки в основном).
Я все еще подозреваю, что это может быть вызвано тем, что алгоритм ThreadPool не создает потоки достаточно быстро, когда происходит всплеск запросов, но, насколько мне известно, мы не можем настроить этот алгоритм.

Было бы замечательно, если бы основная команда asp.net могла создать стресс-тест на всплески для простого веб-приложения, чтобы подтвердить, действительно ли существует узкое место на уровне пула потоков или где-то еще.

Все 129 Комментарий

@sebastienros вы уже создали вики-

Интересно, это потому, что если истощение пула потоков ... Ваше приложение полностью асинхронно? У вас есть еще логи от Пустельги? Можете захватить дамп или профиль? Связан ли ЦП приложения или ввод-вывод?

Это произошло несколько раз раньше, обычно это происходит для нескольких последовательных запросов за короткое время.
Я отслеживаю ЦП, память, количество потоков и множество других показателей в мониторе Azure, и все выглядит нормально.
Таким образом, даже если нет очевидных узких мест и приложение очень быстро реагирует, это все равно может происходить время от времени.
Да, это полностью асинхронно.

У вас есть еще логи от Пустельги?

Других предупреждений / ошибок нет. Вероятно, информационный уровень, но я не настаиваю на этом, хотя, вероятно, есть способ получить их.

Можете захватить дамп или профиль?

Я смогу захватить дамп с лазурного портала, если ты думаешь, что это поможет?
Не знаете, что такое профиль?

Есть мысли о том, как разобраться в этой проблеме?

Мы не уверены, вызваны ли ошибки законными запросами.
Можно ли узнать URL-адрес, который был запрошен при создании этого сообщения?

Мы получаем пару сотен таких в день, хотя все показатели выглядят нормально.

Может быть, какой-либо конкретный источник журнала, который я могу включить в ядре azure / asp.net?

Трассировка стека нам мало что дает.

Exception: Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.TryRead(ReadResult& result)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.OnConsumeAsync()

Была только одна запись в журнале с немного улучшенным стеком вызовов, включая часть нашего вызывающего кода.
Стек указывает на действие нашего веб-перехватчика.
Чтобы убедиться, что веб-перехватчик действителен, мы должны хешировать тело запроса и использовать следующий код.
Что-то не так с этим кодом?

[HttpPost]
        public async Task<OkResult> Webhook()
        {
            var memoryStream = new MemoryStream();
            await Request.Body.CopyToAsync(memoryStream);
            bool isValidRequest = await AuthorizationService.IsAuthenticWebhook(Request.Headers, memoryStream, _shopifyLibOptions.SecretKey);
            if (!isValidRequest)
            {
                throw new UnauthorizedAccessException("This request is not an authentic webhook request.");
            }
            memoryStream.Position = 0;
            string body = new StreamReader(memoryStream).ReadToEnd();

        //omitted code here unrelated to asp.net

            return Ok();
        }

Стек вызовов указывает на строку
bool isValidRequest = await AuthorizationService.IsAuthenticWebhook(Request.Headers, memoryStream, _shopifyLibOptions.SecretKey);
Хотя по моему опыту, номера строк в стеке вызовов иногда немного сбиваются, и мне интересно, действительно ли проблема в строке перед await Request.Body.CopyToAsync(memoryStream);

Тогда стек ниже

Exception: Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.GetReadAsyncResult()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.d__22.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.d__23.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at LINE FROM OUR CODE

.ReadToEnd(); - это красный флаг, блокирующий синхронизацию ввода-вывода. Об этой ошибке скорости передачи данных часто сообщалось в приложениях, у которых возникли проблемы со слишком большим количеством заблокированных потоков пула потоков. В этом случае есть простая замена ReadToEndAsync() . Проверяли ли вы где-нибудь еще проблемы с голоданием потоков?

Но поток здесь - это поток памяти. Вот почему он использует версию синхронизации. Нет ввода / вывода, верно?

Количество потоков невелико, а показатели в норме.
Эта проблема кажется довольно спорадической, но мы получаем эти ошибки партиями по 5 или 10 ...
Может быть, сборщик мусора вызвал паузу?

Ах, ты меня поймал. Я недостаточно внимательно читал.

Насколько велики сообщения?

Это довольно маленькие полезные нагрузки json. Заказ, Клиент, Продукт, все такое ...

Какой размер довольно маленький?

Обычно менее 10 тыс. Символов, хотя есть несколько выбросов, превышающих 100 тыс. Символов.
Однако к нам поступает много запросов. Примерно 500к в день.

Можно ли включить какие-либо дополнительные журналы, чтобы разобраться в этой проблеме?
Это действительно загрязняет наши файлы журналов, и мы до сих пор не уверены, почему это вообще происходит.

Похоже, мы избавились от этой проблемы.
Мы не совсем уверены, но, скорее всего, это было вызвано тем, что какой-то код обращается к объекту запроса из фонового потока.

Мы не совсем уверены, но, скорее всего, это было вызвано тем, что какой-то код обращается к объекту запроса из фонового потока.

Используя IHttpContextAccessor ? Как выглядел этот код?

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

Дэвид, это развертывание включало исправление, связанное с IHttpContextAccessor, которое вы помогли мне решить здесь . Я ошибочно подумал, что это могло решить и эту проблему.

Стек вызовов незначительно меняется, когда ошибка записывается в журнал. Обычно только кадры asp.net, но иногда это происходит и в этом новом фрагменте кода, который очень похож на тот, который я опубликовал выше, обрабатывающий веб-перехватчики:

Немного упрощено для наглядности:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Middlewares
{
    public class RequestLogScopeMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RequestLogScopeMiddleware> _logger;

        public RequestLogScopeMiddleware(RequestDelegate next, ILogger<RequestLogScopeMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {
            using (_logger.BeginScope(await GetHttpInfo(context)))
            {
                await _next(context);
            }
        }

        private async Task<HttpInfo> GetHttpInfo(HttpContext ctx)
        {
            try
            {
                var req = ctx.Request;
                req.EnableRewind();
                string body = await new StreamReader(req.Body).ReadToEndAsync();
                req.Body.Position = 0;
                try
                {
                    body = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(body), Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
                }
                catch
                {
                    //body may not be json
                }
                return new HttpInfo(req.Method, req.GetDisplayUrl(), body, string.Join(Environment.NewLine, req.Headers.Select(kv => $"{kv.Key}: {kv.Value}")));
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to extract http info from request");
                return null;
            }
        }
    }
}

Зарегистрированная ошибка:

Failed to extract http info from request

    HttpInfo: 

    Exception: Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.TryRead(ReadResult& result)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.OnConsumeAsync()

Я почти уверен, что это связано с await new StreamReader(req.Body).ReadToEndAsync();

Вот еще один стек, четко указывающий, что проблема заключается в чтении потока:

Error from Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware:
    An unhandled exception has occurred while executing the request.

    HttpInfo: 

    Exception: Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.ReadAsync(CancellationToken token)
   at System.IO.Pipelines.Pipe.DefaultPipeReader.ReadAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.d__35.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.IO.StreamReader.d__97.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.IO.StreamReader.d__62.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Middlewares.RequestLogScopeMiddleware.d__5.MoveNext()

@ halter73 есть идеи?

PS: вы используете это промежуточное ПО в производстве? Это выглядит ужасно неэффективно.

Да, мы используем это в продукте.
У нас не было проблем с производительностью, но я слушаю, есть ли у вас идеи, как сделать это более производительным.
Журнал тела очень полезен для выяснения причин ошибок в конкретном запросе ...

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

У вас есть структурированный журнал? Вам не нужно прикреплять такой объем информации к области видимости, вам просто нужно приложить достаточно информации к каждому запросу, чтобы вы могли найти все коррелированные журналы. Вы должны регистрировать тело по частям, а затем просматривать всю полезную нагрузку, просматривая все журналы для определенного идентификатора запроса.

У нас не было проблем с производительностью, но я слушаю, есть ли у вас идеи, как сделать это более производительным.

Это DoS ждет своего часа. Я могу просто отправить большую полезную нагрузку в ваше приложение и бум, нехватка памяти. (см. пример https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/Scenarios/Controllers/BigJsonInputController.cs). Это также будет одним из запретов ASP.NET Core (см. Https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AspNetCoreGuidance.md#avoid-reading-the-entire-request-body-or -ответ-тело-в-память)

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

Да, я видел это достаточно раз, и думаю, что нам следует создать что-то более эффективное из коробки https://github.com/aspnet/AspNetCore/issues/3700

У вас есть структурированный журнал? Вам не нужно прикреплять такой объем информации к области видимости, вам просто нужно приложить достаточно информации к каждому запросу, чтобы вы могли найти все коррелированные журналы. Вы должны регистрировать тело по частям, а затем просматривать всю полезную нагрузку, просматривая все журналы для определенного идентификатора запроса.

Я думаю, вы могли бы сказать, что это структурировано, да.
Записывать тело по частям? Я не совсем понимаю, что вы имеете в виду?
Означает ли это, что мне нужно восстановить тело из нескольких записей в журнале?
Конечно, было бы намного проще иметь единственную запись с полным телом.

Кроме того, подавляющее большинство запросов, которые мы получаем, являются веб-перехватчиками, которые могут принимать различную форму (но всегда в формате json), и мы должны проверить их подлинность путем хеширования тела.
(см. метод Webhook() который я опубликовал выше)
Таким образом, мы не можем использовать [FromBody] потому что полезная нагрузка может сильно отличаться от запроса к запросу.

Это DoS ждет своего часа. Я могу просто отправить большую полезную нагрузку в ваше приложение и бум, нехватка памяти.

Я понимаю, как это могло быть проблемой.
Должен ли я использовать что-то вроде BigContentManualGood() из вашего связанного образца?
Насколько я могу судить, он по-прежнему загружает весь json в память в JObject, поэтому я не вижу, чем это отличается?
Я предполагаю, что в ядре asp.net должно быть какое-то разумное ограничение максимального размера запроса по умолчанию?

Возвращаясь к исходной проблеме, я просто хотел указать, что наблюдаемая нами закономерность состоит в том, что эти ошибки возникают в пакетном режиме.
Некоторое время не будет никаких проблем (от 5 минут или даже до пары часов), а затем внезапно мы получим много раз эту ошибку в быстрой последовательности (скажем, от 3 до 10+).
Это может быть полезная информация.
Это заставило меня подумать, что это может быть вызвано более длинной, чем обычно, паузой GC ... Просто идея ...

В чем разница между двумя следующими?

await new StreamReader(Request.Body).ReadToEndAsync();
await new HttpRequestStreamReader(Request.Body, Encoding.UTF8).ReadToEndAsync();

Сегодня мы снова развернулись, чтобы попытаться исправить это.
Мы больше не собираем тело при регистрации, основываясь на хороших комментариях @davidfowl о производительности и безопасности.
Мы удалили все случаи использования HttpAccessor, и у нас остался единственный фрагмент кода, который считывает все тело и вызывает ошибку в нашем действии Webhook() , которое теперь выглядит следующим образом:

        [HttpPost]
        public async Task<OkResult> Webhook()
        {
            var body = await new HttpRequestStreamReader(Request.Body, Encoding.UTF8).ReadToEndAsync();
            bool isValidRequest = AuthorizationService.IsAuthenticWebhook(Request.Headers, body, _options.SecretKey);
            if (!isValidRequest)
            {
                throw new UnauthorizedAccessException("This request is not an authentic webhook request.");
            }
            //handle webhook
            return Ok();
        }

Использование HttpRequestStreamReader не устранило проблему.

Мы также хотели управлять временным исчерпанием пула потоков (даже несмотря на то, что счетчик количества потоков на лазурном портале выглядит нормально), потому что мы думали, что пулу потоков может потребоваться время для увеличения потоков, и, возможно, всплеск запросов может вызвать временное голодание.
Но похоже, что потоков много, и ошибка все еще возникает ...

public override void OnException(ExceptionContext context)
{
    context.ExceptionHandled = true;
    ThreadPool.GetAvailableThreads(out var workerThreads, out var completionPortThreads);
    ThreadPool.GetMaxThreads(out var maxWorkerThreads, out var maxCompletionPortThreads);
    _logger.LogError(context.Exception, $"Error in global mvc exception filter. Available Threads = ({workerThreads:N0}/{maxWorkerThreads:N0}, {completionPortThreads:N0}/{maxCompletionPortThreads:N0}).");
}
Error in global mvc exception filter. Available Threads = (32,757/32,767, 1,000/1,000).

    Exception: Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.TryRead(ReadResult& result)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.OnConsumeAsync()

У нас заканчиваются идеи.

@ clement911 Каков ваш масштаб службы приложений?

У нас, похоже, такая же проблема. Приложение ASP.NET Core 2.1 в развертывании службы приложений Azure (Windows) с аналогичным поведением - нормально в течение нескольких минут, затем все группы подключений выдают это исключение, а затем снова нормально.

Все запросы представляют собой POST с телом JSON, которое считывается и десериализуется в промежуточном программном обеспечении.

Привет @tstuts!
Это развертывание с 3-5 экземплярами S2, и мы получаем массу запросов, в основном веб-перехватчиков.
Несколько 100к в день.

Я не уверен, связано ли это с этой проблемой, но мы также заметили, что получаем гораздо больше из следующих ошибок. Далеко не так много, но раньше мы, возможно, получали это один раз в день, а теперь мы получаем намного больше, и я чувствую, что они могут происходить примерно в то же время, что и первоначальная проблема.

Warning from Microsoft.AspNetCore.Server.Kestrel:
    Heartbeat took longer than "00:00:01" at "11/01/2018 19:48:02 +00:00".

Обычно это означает, что существует нехватка пула потоков ... Хм

@davidfowl Я не понимаю, как это возможно, учитывая, что ThreadPool.GetAvailableThreads сообщает о наличии множества доступных потоков?
Мы не создаем ручные темы.
У нас есть несколько Task.Run (), а также несколько периодических фоновых заданий, запланированных с помощью Observable.Timer , который использует ThreadPool под капотом.

Вот снимок экрана с портала Azure, на котором показано количество наших потоков за последние 12 часов.
Я не считаю, что 200+ потоков - это чрезмерно?

image

200 потоков - это намного больше, чем у вас есть ядра. Если все эти потоки активны, то некоторые из них могут не получать достаточно процессорного времени (аналогично нехватке пула потоков). Мы вносим некоторые изменения в таймеры скорости передачи данных, чтобы смягчить эти сценарии. https://github.com/aspnet/KestrelHttpServer/pull/3070. Тем не менее, ваша система, вероятно, все еще перегружена.

Если это так, то, думаю, увеличение количества экземпляров может решить проблему.

Простите за невежество, но я не понимаю.
ЦП и память мне кажутся здоровыми (см. Скриншоты ниже).
Мы используем экземпляры S2, но портал Azure рекомендует S1.
Я попытался выяснить, сколько ядер у S2, но все, что я могу сказать, это то, что у них есть 200 ACU (вычислительных единиц Azure), что бы это ни значило.

Я прошел через самые последние ошибки MinRequestBodyDataRate, и разница между доступными протекторами и максимальным количеством потоков всегда меньше 100, так что из предположительно разумного (?) Максимума по умолчанию 32 757 рабочих потоков меньше 100! 100/32757 составляет менее 1/3 от 1%.

Я полагаю, я мог бы увеличить количество экземпляров, и, возможно, это решит проблему, но я думаю, я хотел бы понять, в чем проблема, поскольку для меня это совсем не очевидно. (вместо того, чтобы тратить деньги на решение проблемы).

image

image

Вы где-нибудь используете Task.Wait или Task.Result?

Спасибо, @davidfowl, что хорошо

Вы где-нибудь используете Task.Wait или Task.Result?

Нет.
Я буду искать все блокирующие звонки, которые я мог пропустить.

Кстати, мы не используем ядро ​​.net.
Мы используем .net 4.7.1, но я предполагаю, что пул потоков работает аналогичным образом.

@ clement911 да, принципиально то же самое (ядро немного более оптимизировано)

.NET Threadpool ДЕЙСТВИТЕЛЬНО пытается внедрить больше потоков, но делает это с умеренной скоростью (например, 1 или 2 в секунду), поэтому в краткосрочной перспективе это не имеет большого значения (вам нужно много минут, чтобы получить до 1000).

Интересно, проблема ли в этом. Это не похоже на отличную стратегию для борьбы с временными всплесками.

Спасибо, проверю Ben.BlockingDetector.

Все, что делает наш метод webhook, проверяет его подлинность (привязка к ЦП) и сохраняет его в базе данных с помощью ядра ef (асинхронный ввод-вывод).

Я заметил, что когда возникает ошибка MinRequestBodyDataRate, это, вероятно (но не всегда), в сочетании с предупреждением для нескольких других веб-перехватчиков о том, что сохранение в базу данных заняло абсурдное количество времени (скажем, более 40 секунд и даже до 180 секунд!), Чтобы сохранить данные веб-перехватчика.
Я подумал, что это может быть стратегия повторных попыток ядра EF, которая выполняет несколько повторных попыток, но, насколько я могу судить, база данных очень отзывчива, и я не нашел события в ядре EF для регистрации при повторной попытке ...
Это похоже на странное совпадение, поэтому я предполагаю, что это может быть связано с исчерпанием того же потока.

Да, эти симптомы звучат в точности так, как будто продолжается голод. Выполнение дампа памяти при возникновении проблемы может помочь в проверке. Если у вас так много потоков, вы также можете посмотреть, что они делают, и это может намекнуть на проблемную область.

Сделаю спасибо, ребята, я очень ценю помощь

Какой код состояния HTTP будет возвращен клиенту при возникновении исключения MinRequestBodyDataRate / BadHttpRequestException?
Я бы подумал, что 4xx? (хотя в моем случае это, скорее всего, внутренняя проблема)

Мы получаем много таких ошибок в наших журналах, но метрики портала Azure утверждают, что ответов HTTP 4xx или 5xx практически нет. Я не понимаю.

Я получил дамп памяти из лазурного портала (дважды) в надежде, что он прольет свет, но в окне параллельных стеков не видно ничего очевидного. Очень мало потоков (7) с управляемым стеком, и ничто не блокирует, кроме Program.Main и пары других обычных подозрительных потоков (таких как очередь ConsoleLoggerProcessor).
Все остальные цепочки показывают «Недоступно» в окне цепочек.

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

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

Из-за отказа я (неохотно) включил Application Insights, который, как я прочитал, мог собирать метрики без повторного развертывания. Это не сработало. Ни один из показателей не отображает никаких данных. Возможно, мне придется изучить конфигурацию аналитики приложения во время сборки.

Я все равно не знаю, могу ли я доверять метрикам Azure. Как я уже упоминал выше, монитор Azure не сообщает об ошибках http ...

Проблема, кажется, становится все более и более серьезной с увеличением активности, которую мы получаем с течением времени.
: weary:: confused:

Приведенная ниже метрика из дампа привлекла мое внимание, но в стеке потока финализатора нет пользовательского кода.

image

Кстати, я был удивлен, увидев, что 3 потока были заняты ConsoleLogger MessageProcessor Queue. Это заставило меня задуматься, почему он включен в продукте по умолчанию ...

Так что я боролся с этим и хотел сообщить о своих выводах.

Нам нужно было действовать быстро, поэтому мы решили развернуть точную копию нашего приложения в новом плане службы приложений.
Затем мы переместили все ваши подписки на веб-перехватчики, чтобы они указывали на новый экземпляр.
Таким образом, исходная служба приложений обрабатывает только запросы пользователей, что делает ее очень стабильной. Ошибок нет.
Новая служба приложений обрабатывает веб-перехватчики и НИЧЕГО НЕ ДЕЛАЕТ.
Новая служба приложения даже не доступна пользователям, и единственные запросы, которые выполняются, - это простое действие Webhook (), которое выполняет хэш тела и сохраняет тело в базе данных, выполняя асинхронный вызов через SqlCommand с await cmd.ExecuteNonQueryAsync();
Что ж, ошибка MinRequestBodyDataRate все еще возникает! (на новом экземпляре конечно).

Учитывая, что в новом экземпляре выполняется очень мало кода, я уверен, что мы случайно не вызовем какой-либо API синхронизации и не заблокируем какие-либо потоки.
В логах четко видно, что ошибки возникают при пике запросов.
Я думаю, что проблема может быть либо в пуле соединений sql, либо в пуле потоков.

При обычном пользовательском трафике не ожидается резкого пика запросов. Обычно он увеличивается медленно, и пул потоков кажется неплохим в этом отношении.
С другой стороны, с помощью веб-перехватчиков мы иногда можем получить чрезвычайно внезапные пики, потому что некоторые системы где-то генерировали массу событий.
Но пулы потоков порождают потоки очень медленно.

Я подумываю поэкспериментировать с настройкой ThreadPool.SetMinThreads и ADO.net 'MinPoolSize'.
Хорошей стратегией может быть сохранение большого количества потоков и / или соединений для обработки пиков.
Если у кого-то есть опыт в этой области, я слушаю.
Эти настройки легко установить, но очень мало документации о том, как это работает за сценой, и это кажется очень черным квадратным. Вероятно, хорошая причина, кроме случаев, когда вы сталкиваетесь с подобным сценарием ...

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

Не знаете, как именно вы реализуете дросселирование и как это решит проблему?

Одна из проблем заключается в том, что большинство поставщиков веб-перехватчиков (систем / API-интерфейсов, отправляющих запросы) обычно требуют, чтобы слушатель (например, мы) довольно быстро ответил HTTP 200, или они сочли это ошибкой.
В нашем случае это 5 секунд.

На данный момент это только теоретически, но ASP.NET 4 избежал этого класса проблем, используя аналогичный подход. Обработка ваших запросов не слишком затратна, их просто много, и они конкурируют за ресурсы, такие как потоки, время процессора, память и т. Д. Если вы помещаете эти запросы в очередь и обрабатываете только несколько за раз, тогда нет более длительный спор, и они могут завершиться быстро. Наивной версией этого было бы промежуточное ПО, которое использовало такой механизм, как SemaphoreSlim, для ограничения количества одновременно обрабатываемых запросов. Пока все идет гладко, нет причин, по которым вы не сможете своевременно обрабатывать большие всплески. Вам просто нужно не дать взрыву раздавить вас.

@ clement911 можете ли вы воспроизвести проблему на сайте с помощью веб-хуков, если вы забьете ее с помощью аналогичного трафика?

@Tratcher , это интересное предложение. Но если запрос ожидает SemaphoreSlim, это будет учитываться в потоках ввода-вывода ThreadPool.
Я думаю, что сложнее всего будет найти оптимальное количество потоков в пуле для достижения максимальной пропускной способности.

@davidfowl в обеих службах приложений размещен один и тот же код. Я попытался воспроизвести проблему локально с помощью простого запроса на выдачу локальной программы, но это не сработало. Вероятно, можно воспроизвести с помощью подходящего инструмента нагрузочного тестирования, если вы об этом просите.

У SemaphoreSlim есть асинхронный официант, поэтому он не блокирует потоки.

@davidfowl Я думаю, что у меня очень похожая проблема с 2.1.5, обрабатывающая веб-перехватчики Azure DevOps в тестовой веб-службе. Количество запросов невелико (пакетов по 6 только при построении очереди). В моем случае он воспроизводит 5-10% пакетов. Я снова читаю / хэширую тело для проверки на подпись.

@ clement911 @mmitche Ребята, вы тоже часто видите это исключение?

Microsoft.AspNetCore.Connections.ConnectionResetException: существующее соединение было принудительно закрыто удаленным узлом

@tstuts да.

Я предполагаю, что запрос, который не обрабатывается достаточно быстро, приведет к тайм-ауту на стороне клиента и закрытию соединения, что приведет к этому исключению. Еще один симптом той же основной проблемы. Это звучит правильно, @davidfowl?

Учитывая очень острую природу этой ошибки, я также задавался вопросом, может ли это быть Azure, выполняющая некоторые периодические задачи на виртуальной машине, которые могут вызывать это ... Я помню, как читал где-то о некоторых задачах диагностики, которые могут вызывать скачки на машине каждый раз. так часто...

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

Мы также сталкиваемся с этим с API, который берет GeoJSON и записывает его в базу данных SQL Azure. Запросы поступают пачками, и одновременно поступают сотни запросов, потому что мы разделяем данные по пачкам, чтобы избежать превышения пределов длины запроса.

Это также похоже на нехватку потоков, мы работаем на одном экземпляре S3:
image

Я постараюсь решить проблему, основываясь на информации в этом обсуждении, и в случае успеха я сообщу здесь о своих выводах.

Пока не о большом прогрессе, о котором можно сообщить. Мне нужно исправить мой предыдущий комментарий: мы размещаем ряд приложений ASP.NET и ASP.NET Core в одной службе приложений как виртуальные приложения.

Я предположил, что проблема заключается в одном конкретном API, поскольку он получает большую часть нагрузки, но я не обнаружил явных блокирующих вызовов. Я опробовал Ben.BlockingDetector на своем локальном компьютере, но почти все зарегистрированные случаи, по-видимому, были вызваны журналированием (в трассировке стека появилось сообщение «ETL»).
Я также пробовал PerfView, но мне не удалось получить от него желаемый результат.

Что касается количества потоков, мы достигаем в среднем 180 в режиме ожидания из-за w3wp + различных приложений, работающих на нем. Так что, возможно, в этой области нет большой проблемы. Я продолжу нагрузочное тестирование и диагностику, чтобы определить источник проблемы.

Мы видим те же сообщения об ошибках, но мы смогли воспроизвести их, используя профили Chrome Network Throttling. После создания одного со скоростью 1 Кбит / с (вверх / вниз) и задержкой 5 КБ мс мы смогли увидеть эффекты получения исключений BadHttpRequestExceptions со следующим параметром «Время чтения тела запроса истекло из-за слишком медленного поступления данных. См. MinRequestBodyDataRate».

В нашем производственном случае проблема заключалась в мобильном клиенте с очень нестабильным подключением к Интернету и загрузке данных.

@davidfowl Жесткое исключение - лучший способ справиться с этим? Нужно ли нам просто промежуточное ПО для обработки медленных клиентов и проглатывания исключения?

Как и в нашем случае, сервисы приложения повторно используются (предполагается, что из-за чего-то вроде быстрой защиты от сбоев). Я не припомню ничего подобного в IIS 7, работающем под управлением ASP.NET 4.5. Хотя IIS отключил бы пул приложений и полностью отключил бы приложение ...

Та же проблема и у меня.
Вот мой код, обслуживающий загрузку фотографий с нескольких камер, подключенных через GPRS (включение и выключение для экономии энергии):

        [HttpPost("photo/post")]
        public async Task PostLegacyAsync()
        {
            var content = new MultipartFormDataContent();
            foreach (var pair in this.Request.Form)
            {
                content.Add(new StringContent(pair.Value), pair.Key);
            }
            foreach (IFormFile ff in this.Request.Form.Files)
            {
                byte[] data;
                using (var br = new BinaryReader(ff.OpenReadStream()))
                {
                    data = br.ReadBytes((int)ff.OpenReadStream().Length);
                }
                content.Add(new ByteArrayContent(data), ff.Name, ff.FileName);
            }

            // ...

            this.Ok();
        }

Эта служба считается устаревшей для нашей системы, но есть еще несколько устройств, которые нужно обновить. Предложения по его улучшению приветствуются.

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

Обновление до последней версии System.IO.Pipelines устранило проблему для меня.

@highfield вам следует избегать использования Request.Form и вместо этого использовать ReadFormAsync

Всем привет,

У нас такая же проблема в производстве, 2 экземпляра S1. В нашей среде это похоже на всплески соединений / запросов, см. Прилагаемую диаграмму. Ошибки сервера на диаграмме - это те, которые обсуждаются в этом выпуске. Т.е.:

BadHttpRequestException: Reading the request body timed out due to data arriving too slowly

image

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

Мы наблюдаем ту же проблему в производственной среде, а также связанный с ней BadHttpRequestException вызывающий ответ 400: «Неожиданный конец содержимого запроса». Мы мало понимаем, что вызывает эту проблему, но всего 1 запрос в секунду с телом запроса 600 КБ вызовет ее. Похоже, что это усугубляется ненадежным клиентским подключением, например, через мобильные / сотовые сети 3G. Мы не видим связанных всплесков потребления ресурсов ЦП или памяти в то время, когда они происходят.

Пытались отследить эту проблему относительно того, почему контейнеры ASP.NET Core 2.1 продолжают давать сбой на K8s. Думал, что это прервет запрос, но удивился, что это, похоже, приводит к сбою процесса ...

Прерывание запроса не приводит к сбою процесса

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

@ clement911 Мне удалось исправить эту проблему в своем рабочем проекте, отказавшись от использования HTTP2 в конфигурации Kestrel в Program.cs. Не могли бы вы ответить об эффекте - мне это очень интересно, потому что в компании только наш проект имеет эту проблему.

@ Chakalaka11, мы не включили http 2, но не могли бы вы скопировать сюда свой код?

@ clement911 Итак, за последние часы команда внесла некоторые изменения, удалив UseKestrel, используя ConfigureKestrel для повторного присоединения http2, тоже нормально работает. Парень, который сделал исправление, говорит, что UseKestrel мог переписать некоторую стандартную конфигурацию и допустить ошибку.
image

Может это поможет
Сегодня я проведу несколько регрессионных тестов, чтобы проверить, как все работает, и обновить статус, если все работает нормально.

UPD: Работают нормально.

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

Хорошо, если у меня будут какие-то обновления по этой ошибке - я дам вам знать. Надеюсь, вы найдете свое решение.

Хочу присоединиться к "вечеринке".
В нашем коде мы делаем:
C# await context.Request.Body.CopyToAsync(requestBodyStream);
Ничего синхронного с потоком запросов. Мы получаем ошибку «Истекло время чтения тела запроса из-за слишком медленного поступления данных. См. MinRequestBodyDataRate».

Какие выводы могут нам помочь? Я прочитал ветку выше, и похоже, что основная причина пока неизвестна.

Итак, я смотрю на это . Кто-нибудь пробовал установить более низкое значение для MinRequestBodyDataRate ?
Кроме того, наше приложение действительно работает с медленными сетевыми соединениями (по задумке). Так, может быть, нам следует дать команду клиентской части повторить попытку, когда соединение улучшится? Опять же, это может относиться только к определенным типам приложений, и, возможно, мы в их числе.

Я пробовал следующее в методе Startup.cs ConfigureServices ...

services.Configure<KestrelServerOptions>(options =>
            {
                options.Limits.MinRequestBodyDataRate = new MinDataRate(1, TimeSpan.FromMinutes(5));
                options.Limits.MinResponseDataRate = new MinDataRate(1, TimeSpan.FromMinutes(5));
                options.Limits.MaxRequestBodySize = 100000000;
            });

До внесения этого изменения у меня были основные контейнеры asp.net в Kubernetes, которые вылетали / перезапускались 90 раз в час, причем последним сообщением в журнале контейнера была трассировка стека BadHttpRequestException . (Как сказал @davidfowl выше, это казалось действительно странным, потому что я ожидал, что это исключение произойдет на уровне запроса и не приведет к сбою процесса ... поэтому я склонен думать, что, возможно, что-то еще способствовало сбою?)

После внесения этого изменения этой проблемы не было. Если эта проблема не исчезнет, ​​мой план состоял в том, чтобы попытаться поставить перед сервером Kestrel какой-то обратный прокси-сервер, который буферизует медленный входящий поток данных, но до этого еще не приходилось переходить.

Приятно слышать, что некоторые находят решения.
Лично я не люблю применять обходные пути, которые я действительно не понимаю, без предварительного понимания проблемы.

Насколько я понимаю, при большой нагрузке ошибка «Время ожидания чтения тела запроса истекло из-за слишком медленного поступления данных» не является реальной причиной. Это следствие нагрузки на систему.
Я до сих пор не понял, ПОЧЕМУ система испытывает стресс, учитывая, что показатели выглядят нормально (ЦП, память и потоки в основном).
Я все еще подозреваю, что это может быть вызвано тем, что алгоритм ThreadPool не создает потоки достаточно быстро, когда происходит всплеск запросов, но, насколько мне известно, мы не можем настроить этот алгоритм.

Было бы замечательно, если бы основная команда asp.net могла создать стресс-тест на всплески для простого веб-приложения, чтобы подтвердить, действительно ли существует узкое место на уровне пула потоков или где-то еще.

Нам пришлось масштабировать наши экземпляры Azure, даже если они выглядят нормально, только потому, что мы получаем их огромное количество, а также ошибки «Соединение принудительно закрыто» и «Обработка соединения завершилась ненормально».

Это происходит для нас в
Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.<PumpAsync>
вызванный
Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.<ReadAsync>
вызванный
Microsoft.AspNetCore.WebUtilities.FormReader.<ReadFormAsync>
вызванный
Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenStore.<GetRequestTokensAsync>
что означает, что это происходит при проверке csrf.

Больше трассировки стека:
Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate. at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.<PumpAsync>d__4.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.IO.Pipelines.PipeCompletion.ThrowLatchedException() at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result) at System.IO.Pipelines.Pipe.GetReadAsyncResult() at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.<ReadAsync>d__21.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.<ReadAsyncInternal>d__21.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.IO.StreamReader.<ReadBufferAsync>d__97.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.IO.StreamReader.<ReadAsyncInternal>d__64.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.WebUtilities.FormReader.<BufferAsync>d__41.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.WebUtilities.FormReader.<ReadNextPairAsyncImpl>d__34.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.WebUtilities.FormReader.<ReadFormAsync>d__43.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Http.Features.FormFeature.<InnerReadFormAsync>d__18.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenStore.<GetRequestTokensAsync>d__3.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.<ValidateRequestAsync>d__9.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter.<OnAuthorizationAsync>d__3.MoveNext() -

У нас уже было такое однажды. Операция отправляет строку идентификатора и строку ответа вместе с некоторыми файлами cookie. так действительно маленькое тело.

2019-05-09 11:31:50.9573|30131|TRACE|9.5.3.0|189|Base|BaseController.QuestionsSaveAnswer|27 ms|
2019-05-09 11:32:17.6812|30131|TRACE|9.5.3.0|97 |Base|BaseController.QuestionsSaveAnswer|24 ms|
2019-05-09 11:32:27.2114|30131|ERROR|9.5.3.0|106|Base|BaseController.GetErrorMessage|Error Page Path=/questionsSaveAnswer, error = Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.PumpAsync()
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.GetReadAsyncResult()
   at System.IO.Pipelines.Pipe.DefaultPipeReader.GetResult(Int16 token)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.ReadAsyncInternal(Memory`1 buffer, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.WebUtilities.StreamHelperExtensions.DrainAsync(Stream stream, ArrayPool`1 bytePool, Nullable`1 limit, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
   at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(ModelBindingContext bindingContext)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---

Мы запускаем внутрипроцессный ASP Net Core 2.2.0 на IIS. В журнале число перед | Базой | - это идентификатор потока.

Наконец-то мы нашли исправление!
Как мы и подозревали, это было связано с тем, что пул потоков недостаточно быстро наращивал потоки.
Мы добавили следующие две строки, и это сразу устранило проблему:

ThreadPool.SetMaxThreads(32_767, 2_000);
ThreadPool.SetMinThreads(1_000, 1_000);

Алгоритм наращивания пула потоков очень консервативен, как и минимальное количество потоков по умолчанию.
Я не совсем уверен, нужно ли нам наращивать рабочие потоки. Возможно, потоков io будет достаточно, так как загрузка ЦП очень низка.
Итак, я считаю, что запросы ввода-вывода помещались в очередь внутри пула потоков при пике.
Я рад, что мы нашли решение, но, несмотря на то, что исправление крошечное, потребовалось время, чтобы его найти.

ИМО по умолчанию слишком консервативны.
Для обычного веб-сайта, который может работать нормально, но для приемника API или веб-перехватчика будут всплески, которые должны нарастать намного быстрее, чем 2 потока в секунду.

Не помогает то, что ThreadPool непрозрачен, а документация очень ограничена.

cc @stephentoub @kouvel

Это отличная статья, в которой описывается, что является причиной этого и почему увеличение минимума должно быть лишь временной мерой.
https://blogs.msdn.microsoft.com/vancem/2018/10/16/diagnosing-net-core-threadpool-starvation-with-perfview-why-my-service-is-not-saturating-all-cores- или-кажется-заглохнуть /

Да, спасибо, я прочитал эту статью, и это действительно отличная статья.
В нем говорится, что увеличение минимума потоков является временной мерой, если нехватка
Но в нашем случае мы никогда не блокируем. Мы используем только API асинхронного ввода-вывода.
Итак, насколько я могу судить, единственный способ справиться с внезапным всплеском - это увеличить минимум потоков.
Теперь мы уменьшили масштаб до более дешевого экземпляра Azure и увеличили его с 4 до 2 экземпляров, и мы больше не получаем ни одной ошибки. Это замечательно!
Что интересно, количество потоков существенно не увеличилось, но оно может увеличиваться гораздо быстрее, чем раньше.
Также обратите внимание, что количество потоков ниже установленного нами минимума 1000, потому что пул потоков не порождает их, если в очереди нет элементов, ожидающих.
По крайней мере, я так понимаю.
Может быть полезно иметь возможность создавать экземпляры пулов потоков и давать им разные варианты для разных рабочих нагрузок ...

Я очень удивлен, что вы видите голодание без блокировки, даже для всплесков.

@ clement911, возможно, попробуйте использовать https://github.com/benaadams/Ben.BlockingDetector, чтобы узнать, какой код фреймворка блокирует

Единственные вещи, которые, как мне известно, могут вызвать нехватку потоков, - это блокировка или длительные рабочие элементы, связанные с процессором / памятью в пуле потоков. @ clement911, поскольку вы упомянули, что загрузка ЦП низкая, без дополнительной информации я бы предположил, что происходит блокировка.

Блокировка не обязательно должна происходить из синхронного ввода-вывода. Это может быть блокировка, которая удерживается дольше, чем ожидалось, прямые зависимости в задачах в очереди (задача планирует другую задачу и синхронно ожидает ее завершения) или вообще любое синхронное действие, которое занимает некоторое время.

Наконец-то мы нашли исправление!
Как мы и подозревали, это было связано с тем, что пул потоков недостаточно быстро наращивал потоки.
Мы добавили следующие две строки, и это сразу устранило проблему.

Это предлагаемый обходной путь, когда невозможно устранить основную причину. Это вполне может быть подходящим решением, если происходит блокировка из-за недостатков в пуле потоков, когда речь идет о рабочих элементах, которые блокируются на относительно длительные промежутки времени. Решение может быть контрпродуктивным, если блокировки не происходит. Опять же, поскольку это не то, что вы видите, похоже, что происходит блокировка.

Это может помочь посмотреть на блокировку, которая, вероятно, происходит с трассировкой PerfView, включая Thread Time (которая включает события переключения контекста и время, затраченное на блокировку).

Спасибо, я ценю комментарии.

Некоторое время назад мы не знали, что делать с этой проблемой, но знали, что она связана с тем, что действие контроллера приемника веб-перехватчика получает много запросов.
Поэтому мы развернули еще один отдельный экземпляр службы приложений Azure специально для получения веб-перехватчиков.
Итак, у нас есть основная служба asp.net с одним контроллером и одним вызываемым действием.
(Мы оставили остальную часть кода, но вызывается только действие веб-перехватчика, и оно не обслуживает запросы пользователей и не запускает какие-либо задания.)
Все, что он делает, это проверка заголовков, чтобы убедиться, что запрос подлинный, и сохранение полезной нагрузки в базе данных sql azure с помощью асинхронного вызова.
Вот и все.
Вот почему я совершенно уверен, что у нас нет никаких блокировок.

На ваш взгляд, конечно, async улучшает масштабируемость, но все еще есть ограничение на количество запросов, которые могут быть обработаны, верно?
В нашем случае всплеск может быть очень внезапным и очень большим.
Если пользователь инициирует импорт CSV в Shopify, Shopify мгновенно отправит нам 1 запрос на строку.
Мы могли получить 1000 запросов, попадающих в один экземпляр за минуту. Неужели в этих условиях пул потоков не начнет ставить запросы в очередь?

При работе в IIS есть очередь запросов глубоко в ядре (http.sys), но в ядре ASP.NET очереди запросов нет. Единственная очередь - это очередь пула потоков. Исключение, которое вы видите, исходит от пустельги (поэтому я предполагаю, что вы используете модуль IIS вне процесса). Если вы все еще можете воспроизвести проблему, трассировка будет отличной. Мы просто добавляем счетчики в .NET Core 3.0, что сейчас бесполезно для вас, но в будущем с этими счетчиками подобная вещь станет более очевидной.

кстати, я имел в виду очередь пула потоков.

Возможно, вы могли бы поместить запросы от Shopify в свою собственную фоновую очередь и обрабатывать их один за другим. Таким образом, у вас никогда не будет шипа.
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2

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

@vankampenp
Проблема в том, что мы должны быть уверены, что запросы веб-перехватчика сохраняются, прежде чем мы сможем ответить HTTP 200 на Shopify.
Если мы просто поместим его в очередь памяти и приложение перезапустится по какой-либо причине, мы потеряем запросы.

@ clement911 Я не знаю вашей конкретной ситуации. В нашем сценарии мы сохраняем ключ, затем помещаем его в очередь, которая устанавливает флаг, как только это будет сделано. Мы проводим ежедневный сбор данных, чтобы увидеть, есть ли какие-нибудь ключи без флага.
Это имеет смысл только в том случае, если работа, которую нужно выполнить, занимает много времени.

даже если это всего лишь ключ, суть в том, что вам нужно сохранить хоть что-то, поэтому требуется ввод-вывод.

У нас была такая же проблема, и мы решили ее, переместив чтение тела запроса из контроллера.

В нашем случае у нас был REST API большого объема, где сначала тело запроса читалось внутри контроллера с помощью _HttpRequestStreamReader_. При более высокой нагрузке каждые несколько дней мы видели эту ошибку:

BadHttpRequestException: время чтения тела запроса истекло из-за слишком медленного поступления данных

А также столкнулся с резким падением пропускной способности за это время. Это произошло случайным образом и исчезло само по себе примерно через 10 минут.

С помощью инструмента BlockingDetector мы смогли идентифицировать некоторую минимальную блокировку где-нибудь в структуре, читая таким образом тело запроса.

Из-за этого мы создали InputFormatter, который позволял читать тело запроса в формате json как строку и извлекать тело запроса как параметр в контроллере следующим образом:

public async Task<IActionResult> ProcessAsync([FromBody]string jsonBody = null)

После применения этого обходного пути BlockingDetector больше не обнаруживал блокировку, и в течение трех недель у нас больше не было BadHttpRequestExceptions.

С помощью инструмента BlockingDetector мы смогли идентифицировать некоторую минимальную блокировку где-нибудь в структуре, читая таким образом тело запроса.

Вы можете поделиться результатами этого?

Да, это был результат работы инструмента:
image

А вот предыдущий код контроллера, который это вызвал:
using (HttpRequestStreamReader reader = new HttpRequestStreamReader(Request.Body, Encoding.UTF8)) { _jsonBody = await reader.ReadToEndAsync(); }

используя (HttpRequestStreamReader reader = new HttpRequestStreamReader (Request.Body, Encoding.UTF8)) {_jsonBody = await reader.ReadToEndAsync (); }

Вы написали этот код в своем контроллере?

Да, это был код с блокировкой.

@berndku, почему

@davidfowl : производительность, не все наши запросы требуют полной

@davidfowl : производительность, не все наши запросы требуют полной

Производительность перегружена? Лучшая пропускная способность за счет использования памяти? Буферизация всего тела в памяти означает, что у вас есть эта временная строка только для того, чтобы превратить ее в другой объект. Встроенные средства форматирования не блокируются, они стараются изо всех сил избегать буферизации в один непрерывный блок памяти (например, строку или byte []).

В любом случае, я сообщу о проблеме с поведением ReadToEndAsync https://github.com/aspnet/AspNetCore/issues/13834

Благодарим за подачу заявки!

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

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

https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AspNetCoreGuidance.md#avoid -reading-large-request-body-or-response-органы-в-память

😄

@davidfowl Эта ссылка предоставляет контекст, чтобы _почему_ не делать этого (отлично! 😃), но не _что делать вместо этого_ (о 😞). Допустим, вам нужно принять произвольные тела файлов в запросе и отправить это произвольное тело запроса файла в другую систему, такую ​​как база данных или очередь сообщений. Как избежать чтения этого запроса в памяти?

Конечно, это не очень хорошая практика, но размер запросов составляет около 20-40 КБ, поэтому проблем с фрагментацией LOH не ожидается. Некоторое время назад я рекомендовал перенести трассировку тела в другое место в архитектуре, но это займет больше времени, и нам нужно было краткосрочное решение.

Поскольку # 13834 касается _HttpRequestStreamReader_, я снова проверил наш пользовательский форматировщик (код ниже), но там мы просто использовали _StreamReader_. Может быть, в этом причина того, что мы не видим блокировки с помощью кастомного средства форматирования?

Если да, значит ли это, что нам следует избегать _HttpRequestStreamReader_, пока проблема не будет исправлена?

 public class RawJsonBodyInputFormatter : InputFormatter
    {
        public RawJsonBodyInputFormatter()
        {
            this.SupportedMediaTypes.Add("application/json");
        }

        public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            var request = context.HttpContext.Request;
            using (var reader = new StreamReader(request.Body))
            {
                var content = await reader.ReadToEndAsync();
                return await InputFormatterResult.SuccessAsync(content);
            }
        }

        protected override bool CanReadType(Type type)
        {
            return type == typeof(string);
        }
    }

мм, извините, если это не связано, но я получаю этот вывод от монитора блокировки при перезапуске IIS. Это происходит несколько раз. Я использую taghelper asp-append-version. После временного удаления сообщения о блокирующем вызове больше не сообщается.

2019-09-10 15: 06: 25.0604 | 35.230.16.48 || WARN | 9.9.19.0 | 39 | Base | BlockingMonitor.BlockingStart | Метод блокировки был вызван и заблокирован, это может привести к истощению пула потоков.
в System.IO.FileStream.Read (массив Byte [], смещение Int32, счетчик Int32)
в System.Security.Cryptography.HashAlgorithm.ComputeHash (поток inputStream)
в Microsoft.AspNetCore.Mvc.Razor.Infrastructure.DefaultFileVersionProvider.GetHashForFile (IFileInfo fileInfo)
в Microsoft.AspNetCore.Mvc.Razor.Infrastructure.DefaultFileVersionProvider.AddFileVersionToPath (PathString requestPathBase, String path)
в Microsoft.AspNetCore.Mvc.TagHelpers.ScriptTagHelper.Process (контекст TagHelperContext, вывод TagHelperOutput)
в Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync (контекст TagHelperContext, вывод TagHelperOutput)
в Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync (TagHelperExecutionContext executionContext)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync (TagHelperExecutionContext executionContext)
в AspNetCore.Views_Shared__Layout. <> c__DisplayClass14_0. <b__1> d.MoveNext ()
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в AspNetCore.Views_Shared__Layout. <> c__DisplayClass14_0.__1 ()
в Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync ()
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync ()
в AspNetCore.Views_Shared__Layout.ExecuteAsync ()
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в AspNetCore.Views_Shared__Layout.ExecuteAsync ()
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync (страница IRazorPage, контекст ViewContext)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync (страница IRazorPage, контекст ViewContext)
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync (страница IRazorPage, контекст ViewContext, логическое значение invokeViewStarts)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync (страница IRazorPage, контекст ViewContext, логическое значение invokeViewStarts)
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync (контекст ViewContext, ViewBufferTextWriter bodyWriter)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync (контекст ViewContext, ViewBufferTextWriter bodyWriter)
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync (контекст ViewContext)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync (контекст ViewContext)
в Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync (ViewContext viewContext, String contentType, Nullable 1 statusCode) at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable 1 statusCode)
в Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync (ActionContext actionContext, представление IView, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable 1 statusCode) at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine) at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable 1 statusCode)
в Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync (контекст ActionContext, результат ViewResult)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync (контекст ActionContext, результат ViewResult)
в Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync (контекст ActionContext)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync (контекст ActionContext)
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync (результат IActionResult)
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync (результат IActionResult)
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext TFilter, TFilterAsync
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync TFilter, TFilterAsync
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync TFilter, TFilterAsync
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext TFilter, TFilterAsync
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters ()
в System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start TStateMachine
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters ()
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next (Состояние и следующий, Область и область действия, Объект и состояние, Логическое значение и isCompleted)
в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter ()
в System.Threading.ExecutionContext.RunInternal (контекст выполнения ExecutionContext, обратный вызов ContextCallback, состояние объекта)
в System.Runtime.CompilerServices.AsyncTaskMethodBuilder 1.AsyncStateMachineBox 1.MoveNext ()
в System.Runtime.CompilerServices.TaskAwaiter. <> c.b__12_0 (действие innerContinuation, задача innerTask)
в System.Threading.QueueUserWorkItemCallback 1.<>c.<.cctor>b__6_0(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.QueueUserWorkItemCallback 1.ExecuteWorkItem ()
в System.Threading.ThreadPoolWorkQueue.Dispatch ()

@alastairs

@davidfowl Эта ссылка дает контекст о том, почему бы этого не делать (отлично! 😃), но не о том, что делать вместо этого (о 😞). Допустим, вам нужно принять произвольные тела файлов в запросе и отправить это произвольное тело запроса файла в другую систему, такую ​​как база данных или очередь сообщений. Как избежать чтения этого запроса в памяти?

Разбивая его на буферы размером менее 85 КБ, а затем передавая его по частям. Это один из приемов, который Stream использует для копирования данных из одного потока в другой поток https://github.com/dotnet/corefx/blob/ee9995f31b684a0c6e5488eceb2500bf0057da89/src/Common/src/CoreLib/System/IO/Stream.cs#L31 -L34.

Это в сочетании с объединением этих буферов приведет к повышению общей производительности, поскольку куча больших объектов не будет агрессивно перемешиваться. Также существует потенциальная угроза безопасности при открытии конечной точки, которая считывает все в память и передает это в другую систему. Любое место, где вы можете ввести произвольный клиентский ввод, является потенциальным местом, где клиенты потенциально могут вызвать отказ в обслуживании, если вы не будете осторожны.

@berndku

Поскольку # 13834 касается HttpRequestStreamReader, я снова проверил наш пользовательский форматировщик (код ниже), но там мы просто использовали StreamReader. Может быть, в этом причина того, что мы не видим блокировки с помощью кастомного средства форматирования?

Да, StreamReader отменяет правильные методы.

.UseKestrel(opt => { opt.Limits.MinRequestBodyDataRate = null; })
Может это сработать?

Есть такая же проблема, когда две службы общаются друг с другом (в пределах localhost). @HengzheLi , это мне не помогло ...

Имея ту же проблему

Всем привет,
Мы начинаем новый проект, который снова предполагает сохранение веб-перехватчиков в базе данных для дальнейшей обработки.
Мы ожидаем небольших объемов полезной нагрузки и большого количества запросов.

Оптимален ли следующий код?

[HttpPost]
        public async Task<OkResult> Webhook()
        {
            var body = await new HttpRequestStreamReader(Request.Body, Encoding.UTF8).ReadToEndAsync();
            //save to Db asynchronously?
            return Ok();
        }

Я тоже получаю эту ошибку, но я думаю, что моя проблема здесь:
blob.UploadFromByteArrayAsync(fileByteArray, 0, fileByteArray.Length).Wait();
против
await blob.UploadFromByteArrayAsync(fileByteArray, 0, fileByteArray.Length);

использование Wait() вызовет блокировку потока при загрузке, тогда как использование await - нет.

@ clement911 - это хороший способ обработки веб-перехватчиков, и было бы здорово, если бы ваша обработка затем выполнялась очередью событий, а не веб-сервером.

Моя проблема заключается в получении URL-адреса, его загрузке и последующей загрузке! Глупо делать все это в веб-запросе. Так что исправление проблемы Wait() будет спорным, как только я переделаю этот код, чтобы просто проверить правильность URL-адреса изображения, а затем сбросить фактические сохранения db и загрузку изображений в обработчик событий.

Но спасибо, что не отставали от этого вопроса!

К вашему сведению, с момента публикации здесь я начал новый проект с Asp Net core 2.2 (предыдущий был 2.1). Кроме того, приложение теперь работает на Ubuntu 18 (предыдущее было 14).

Ни одного провала с того времени.

@davidfowl Правильно ли приведенный ниже фрагмент кода для пересылки загрузки в другую службу? каждая загрузка - это в основном изображение (10-15 МБ) и несколько пар "ключ-значение".

    [HttpPost("photo/post3")]
    public async Task PostV3Async()
    {
        IFormCollection fcoll = await this.Request.ReadFormAsync();

        var content = new MultipartFormDataContent();
        foreach (var pair in fcoll)
        {
            content.Add(new StringContent(pair.Value), pair.Key);
        }
        foreach (IFormFile ff in fcoll.Files)
        {
            byte[] data;
            using (var br = new BinaryReader(ff.OpenReadStream()))
            {
                data = br.ReadBytes((int)ff.OpenReadStream().Length);
            }
            content.Add(new ByteArrayContent(data), ff.Name, ff.FileName);
        }

        var client = this._httpClientFactory.CreateClient(ClientName);
        await client.PostAsync("/photo/post3", content);

        this.Ok();
    }

Мне все еще интересно, как лучше всего асинхронно прочитать все тело запроса.

Мы делали await new HttpRequestStreamReader(Request.Body, Encoding.UTF8).ReadToEndAsync();
но после обновления до net core 3.1 мы получаем эту проблему: https://github.com/aspnet/AspNetCore/issues/13834

Поскольку исправление запланировано для .net 5.0, что является правильным для .net 3.1?

Любая фиксация проблемы?

Поскольку эта проблема была отмечена WhiteSource как уязвимость средней степени безопасности, может ли кто-нибудь кратко рассказать мне, что это за эксплойт? Это ОЧЕНЬ длинная цепочка, восходящая к 2018 году. Спасибо.

У меня такая же проблема

Имея такую ​​же проблему.

Для тех, кто все еще испытывает проблему, вы читали мой пост? https://github.com/dotnet/aspnetcore/issues/4707#issuecomment -557538742

Как и сегодня, у меня не было НИКАКИХ проблем на двух разных серверах в течение более года.

Любое обновление по этой проблеме, я также сталкиваюсь с тем же исключением. Запрос довольно маленький, но пустельга выдает 408 с сообщением «запрос поступает слишком медленно».

"message": "Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.", "exception": {"Type":"Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException", "StatusCode":408, "TargetSite":"Void Throw(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.RequestRejectionReason)", "StackTrace":"   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)\r\n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1ContentLengthMessageBody.ReadAsyncInternal(CancellationToken cancellationToken)\r\n   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.ReadAsyncInternal(Memory`1 buffer, CancellationToken cancellationToken)\r\n   at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)\r\n   at Microsoft.AspNetCore.WebUtilities.StreamHelperExtensions.DrainAsync(Stream stream, ArrayPool`1 bytePool, Nullable`1 limit, CancellationToken cancellationToken)\r\n   at Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)\r\n   at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder.BindModelAsync(ModelBindingContext bindingContext)\r\n   at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)\r\n   at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)\r\n   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)\r\n
    [HttpPost]
    [ProducesResponseType(typeof(BaseResponse<bool>), 1)]
    public async Task<BaseResponse> SyncMsg()
    {

        string data = string.Empty;
        try
        {
            using (var sr = new StreamReader( Request.Body))
            {
                data = await sr.ReadToEndAsync();

                //todo: lenth more then 1024, Decompress
                data = StringHandleHelper.Decompress(data);

                var url = ConfigurationManager.GetValue("CallBackUrl");

pragma warning disable CS4014 // Поскольку этот вызов не будет ждать, текущий метод будет продолжать выполняться до завершения вызова

                Task.Factory.StartNew((obj) =>
                {
                     _messageService.SyncMsg((string)obj, url);
                }, data);

pragma warning restore CS4014 // Поскольку этот вызов не будет ждать, текущий метод будет продолжать выполняться до завершения вызова

                var j_data = JToken.Parse(data);
                var type = j_data["type"].ToObject<PcWxMsgType>();
                var wxid = j_data["wxid"].ToObject<string>();

                return Response2WeChatRobot(wxid, type);
            }
        }
        catch (Exception ex)
        {
            return new BaseResponse() { Code = -2, Msg = "站点:" + ex.Message };
        }
    }

я

Я видел все обсуждения. Есть ли сейчас эффективное решение?

Будет ли это исправлено в следующем выпуске?

Всем привет,
Мы начинаем новый проект, который снова предполагает сохранение веб-перехватчиков в базе данных для дальнейшей обработки.
Мы ожидаем небольших объемов полезной нагрузки и большого количества запросов.

Оптимален ли следующий код?

[HttpPost]
        public async Task<OkResult> Webhook()
        {
            var body = await new HttpRequestStreamReader(Request.Body, Encoding.UTF8).ReadToEndAsync();
            //save to Db asynchronously?
            return Ok();
        }

@ clement911, если

или вы пытались использовать структурированный класс вместо чтения объекта из Stream?

Хорошо, я начал читать эту ветку час назад, и я слышу, что в конце еще не нашел решения. Какое решение сейчас для .NEt 3.1, у меня такая же проблема

Это потому, что проблема превратилась в множество разных проблем 😄. Для этой проблемы нет репликации, и это зависит от среды и скорости клиента (по крайней мере, исходная проблема). Мы знаем, что где-то есть проблема, но непонятно, как мы добиваемся прогресса с предоставленной здесь информацией.

@Gopichandar Я столкнулся с этим и решил проблему следующим образом

  • рефрактор большинства методов с использованием шаблона await + async Task ()

    кажется основной причиной в нашем случае, вызванной тем, что весь ЦП используется для ожидающих задач (в основном связанных с DB / IO) до завершения, и это вызывает исключение с BadHttpRequestException: время чтения тела запроса истекло из-за слишком медленного поступления данных

@davidfowl Я думаю, что мне удавалось воспроизводить эту проблему снова и снова, когда я отлаживал свой собственный код. Я снял небольшой фильм, и Wireshark снимает его. Но не могу поделиться ими здесь, в этом общедоступном репозитории на github, по каким еще частным каналам я могу связаться с вами и командой MS?

Более длинная история:
Совершал несколько миллионов вызовов PUT на наши серверы, и около 0,08% из них не удавалось также после нескольких попыток повторения - у всех было одно и то же сообщение на стороне сервера: “Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate” . Конечно, во время множества звонков сервер не простаивал. Но на следующий день после того, как сервер почти простаивал, я смог принять неудавшиеся вызовы и повторно выполнить их как единый запрос в то время. К моему большому удивлению, оказалось, что не удалось загрузить полезную нагрузку, и я все еще вижу то же сообщение об ошибке в журналах нашего сервера.
Также пытался увеличить тайм-аут, чтобы дать серверу время для поиска свободного потока, но все те же ошибки через 10 секунд вместо 5 секунд по умолчанию:
options.Limits.MinRequestBodyDataRate = new MinDataRate(120, TimeSpan.FromSeconds(10))

После нескольких часов расследования я обнаружил, что есть две вещи, которые я могу изменить, чтобы звонок был успешным:
1) Отключите Ciso VPN.
VPN разделяет полезную нагрузку на более мелкие пакеты TCP (вот это 5 пакетов). Это всегда последний пакет, который нужно повторно передать. См. Изображение, «.135» - это мой компьютер, «.57» - это сервер. (когда я отключен от VPN, он отправит полезную нагрузку в одном пакете TCP). Также был подключен к VPN при совершении нескольких миллионов звонков.
image

2) Измените размер полезной нагрузки на 1 байт - либо добавьте, либо удалите один символ в полезную нагрузку json (конечно, в поле, которое не анализируется на стороне сервера)
Полезная нагрузка по-прежнему разделена на 5 пакетов TCP - из-за подключения к VPN.

Полезная нагрузка составляет ~ 6 Кбайт.
Мы видим эту ошибку не только на моем компьютере с VPN, мы видим ее в запросах наших конечных клиентов по всему миру без VPN. Но это расследование было самым близким к отладке, анализу и пониманию этого сообщения об ошибке, которое мы видим в журналах нашего сервера.

Я не думаю, что это проблема пула потоков на стороне сервера, потому что я могу воспроизводить ее снова и снова, а изменение размера полезной нагрузки на 1 байт может повлиять на результат. А на днях сервер обработал намного больше запросов.

Я видел сообщение об ошибке “Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate” на обоих запросах маршрутов с простой привязкой модели ядра aspnet [FromBody] и маршрутах, которые считывают тело самостоятельно await Request.Body.ReadAsBytesAsync() .

Итак, вопрос в том, что именно в aspnetcore может вызвать эту проблему? и как мне поделиться с вами дополнительными данными, ребята?

Похоже, я нашел обходной путь, пока это не будет исправлено.
У меня были те же проблемы, что и у @ rasmus-s. Он продолжал работать, пока пакеты TCP не были разделены на отдельные, которые (для меня) составляли ровно 1398 байт. Если добавить накладные расходы, это странно близко к ~ 1500 байтам кадра Ethernet.
Проведя небольшое исследование, я нашел сообщение, в котором кто-то предложил проверить размер MTU, что я сделал с помощью ping ip -f -l xxx . Конечно, -l 1473 вернул сообщение Packet needs to be fragmented but DF set , поэтому 1472 кажется пределом, при котором пакеты не будут разделены и исключение не возникает.
Чтобы сравнить свой результат с чем-то другим, я переключился на систему, в которой она работала нормально, и снова выполнил эту команду - на этот раз Packet needs to be fragmented but DF set вернулось. Я даже использовал -l 65500 что является максимумом - без сообщения.
На самом деле кажется, что проблема в MTU.

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

  • Похоже, решение - это большие кадры. Я опубликую обновление, как только проверю это.
Была ли эта страница полезной?
0 / 5 - 0 рейтинги