Все активные запросы зависают, когда есть продолжение задачи, но нет потока, ожидающего его - PullRequest
0 голосов
/ 28 марта 2019

Как видно из заголовка, у меня есть очень необычная ситуация, которую я просто не могу объяснить, поэтому в качестве последнего средства я публикую ее здесь.

Лучше всего проиллюстрировать ее с помощью кода сразу.

У меня есть следующая ситуация где-то в моей реализации IHttpHandler:

var requestData = /*prepare some request data for result service...*/;

//Call the service and block while waiting for the result
MemoryStream result = libraryClient.FetchResultFromService(requestData).Result;

//write the data back in the form of the arraybuffer
HttpContext.Current.Response.BinaryWrite(CreateBinaryResponse(result));

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

public async Task<MemoryStream> FetchResultFromService<T>(T request)
{
   ...//resolve url, formatter and media type

   //fire the request using HttpClient
   HttpResponseMessage response = await this.client.PostAsync(
                                               url,
                                               request,
                                               formatter,
                                               mediaType);
   if (!response.IsSuccessStatusCode)
   {
      throw new Exception("Exception occurred!");
   }

    //return as memory stream
    var result = new MemoryStream();
    await response.Content.CopyToAsync(result);
    result.Position = 0;
    return result;
}

Проблема возникает, когда для ответа FetchResultFromService требуется более 2 минут.В этом случае IIS прерывает заблокированный поток (для таймаута IIS установлено значение 120 секунд) с ThreadAbortException .Клиент (браузер) получает ответ об ошибке, и на данный момент все выглядит нормально, но вскоре после этого производительность приложения падает, а время отклика стремительно растет.

Глядя на журналы «Библиотечного сервиса», становится ясно, что момент, когда сервис окончательно завершен (2+ минуты) и возвращает ответ, - это момент, когда происходит аномалия на веб-сервере.Проблема обычно решается сама через одну или две минуты после начала.

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

Если я изменяю код библиотеки с помощью ConfigureAwait(false) , эта проблема не возникаетt происходит.

HttpResponseMessage response = await this.client.PostAsync(
                                               url,
                                               request,
                                               formatter,
                                               mediaType).ConfigureAwait(false);//this fixes the issue

Так что теперь похоже, что это связано с синхронизацией контекста.Приложение использует старый LegacyAspNetSynchronizationContext , который блокирует объекты HttpAplication, но я не могу понять, как это может повлиять на все потоки / запросы.

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

Дополнительная информация

  • Мы не говорим о тупиках из-за блокировки.Вышеуказанная ситуация выполняется без проблем, если службе удается вернуть результат в те 2 минуты, которые IIS позволяет ему сделать. Проблема может быть воспроизведена, даже если вы не блокируете и просто вызываете метод библиотеки, не вызывая Result getter .
  • Удаление секундного ожидания (копирование MemoryStream) уменьшает воспроизведениеОцените это много, но это все же возможно при одновременном выполнении нескольких вызовов Library FetchResultFromService.ConfigureAwait(false) в настоящее время является единственным надежным «решением».
  • Я даже обнаружил, что IIS даже явно разрывает соединения ACTIVE.Я использовал JMeter для стресс-тестирования приложения, и когда происходит аномалия, потоки JMeter (фальшивые пользователи) сообщают, что соединение было сброшено.При ручном тестировании с Chrome я также заметил это падение с ответом ERR_CONNECTION_RESET.Эти сбросы происходят регулярно, когда приложение находится под нагрузкой и возникает аномалия, но, как уже упоминалось выше, аномалия всегда проявляется как снижение производительности для большинства активных пользователей.
  • Приложение представляет собой приложение WebForms, которое реализует и использует пользовательскийсинхронный IHttpHandler для большинства своих запросов.Реализация обработчика объявлена ​​как IRequiresSessionState , обеспечивающая эксклюзивный доступ к сеансу.Я упоминаю об этом, так как у меня есть догадка, что это может быть связано с состояниями сеанса, хотя я не могу это подтвердить.Или, может быть, это связано с вызовом асинхронных методов из обработчиков синхронизации, хотя все равно хотелось бы знать, как.
  • Версия IIS 7, и журналы IIS не показывают ничего полезного (или вовсе).

У меня нет идей для тестирования, надеюсь, кто-то хотя бы подскажет мне в каком-то направлении.Я бы очень хотел это понять.Спасибо!

Ответы [ 2 ]

0 голосов
/ 01 апреля 2019

Семантика использования async / await с LegacyAspNetSynchronizationContext буквально не определена.Другими словами, здесь есть тонны крайних случаев и проблем, в том смысле, что вы не можете ожидать, что что-то сработает вообще.

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

0 голосов
/ 29 марта 2019

Я, к сожалению, полностью пропустил необработанное исключение, которое было причиной закрытия приложения, и я не знал об этом:

Информация об исключении: System.NullReferenceException в System.Web.ThreadContext.AssociateWithCurrentThread(Логическое значение) в System.Web.HttpApplication.OnThreadEnterPrivate (логическое значение) в System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossblyUnderLock (System.Threading.SendOrPostCallback, System.Oalle.Object) в System.Web.LegacyAspNetSynchronizationContext.Post (System.Threading.SendOrPostCallback, System.Object) в System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAction (System.Obas..ContextCallback, System.Object, System.Threading.Tasks.Task ByRef) в System.Threading.Tasks.AwaitTaskContinuation + <> c.b__18_0 (System.Object) at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object) в System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) в System.Thuntion.ThuntionTenttionThThtionThThtionThThtionThThition.Threading.ExecutionContext, System.PerformWaitCallback ()

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

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

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