Доброго времени суток! В этой статье хотел бы поделиться решением неожиданной проблемы, возникшей в одном из проектов, над которым я сейчас работаю.
Что может быть опасного в обновлении .net 4.6.1 до .net 4.6.2? Я считал что для процесса обновления минорной версией достаточно прочитать release notes, чтобы избежать серьезных проблем. Однако, как выяснилось, Microsoft может привнести очень интересные и занимательные изменения в обход release notes, которые смогут вас занять в «скучные летние вечера и выходные дни».
Под катом будет описание одной проблемы возникшей из-за обновления и пути её решения, а так же немного исходников .net.
С чего началось?
Штатное обновление .net. Зачем? Да просто потому что. Можно долго рассуждать на тему «работает — не трогай», но я считаю что на активных проектах всегда необходимо хоть сколько-нибудь регулярно обновлять компоненты стека. Иначе в какой-то момент обновления через несколько десятков версий превратятся в такую боль, что будет проще навсегда заморозить старую версию, чем пытаться обновить и разгребать проблемы, которые коммюнити лечила несколько лет назад и никто уже не помнит, с чем там приходилось сталкиваться.
Вообще говоря, плановое обновление должно было быть на .net 4.7, но поскольку Microsoft очень дружелюбно уже полгода перестраивает систему партнерства и до сих пор не может предоставить способа его нормально продлить в наших условиях, то VS 2017 нам так и не явился пока. Поэтому временно было решено догнать версию хотя бы до 4.6.2.
Сказано — сделано. Обновили, выкатили на тестовый контур, протестировали, потом на боевой. Полет нормальный.
Проблемы
System.Web.HttpException (0x80004005): The request queue limit of the session is exceeded.
Эм, wat? Ежедневно проводимый анализ наиболее частых ошибок выявил на несильно нагруженном сервере 600+ таких ошибок. Хорошо, начинаем копать.
Параллельно была включена поддержка http/2 на мобильных устройствах и в стеке оказался старый мобильный asmx сервис, поэтому следы слегка запутались. Появилось подозрение на то, что банально запросы стали приходить слишком часто и это перегружало очередь.
Начали отрабатывать этот кейс. Поскольку все мобильные сервисы с сессией работали readonly, переводим сессию в соответствующий режим.
Global.asax:
if (<Запрос на падающий сервис>)
{
Context.SetSessionStateBehavior(SessionStateBehavior.ReadOnly);
}
Обновляем контур. Проблемы с мобильными устройствами исчезают. Но возникают с другим asmx сервисом, который используется из web.
Это наводит подозрения, что первичное предположение скорее всего ведёт не в ту сторону.
Гугл — на помощь!
www.google.ru/search?q=The+request+queue+limit+of+the+session+is+exceeded
Что мы видим? Рекомендации увеличить requestQueueLimit (размер очереди на сервер), пробуем — толку нет. Да и как-то по нагрузке не похоже, что мы можем этот лимит пробить каким-то образом.
Вторая ссылка — если у вас проблемы с очередью — «не трожьте очередь, увеличивайте ресурсы!» Понятно, проехали.
Больше никакой информации. Чтож, остается еще один проверенный способ. Исходники.
Я на самом деле очень благодарен MS за открытие исходников. Иначе бы многие проблемы решались бы в десятки раз дольше, эмпирическим способом, а может так и остались бы нерешенными. (А еще для путешествия по исходным кодам очень удобен Resharper).
Вводные данные:
System.Web.HttpException (0x80004005): The request queue limit of the session is exceeded.
at System.Web.SessionState.SessionStateModule.QueueRef()
at System.Web.SessionState.SessionStateModule.PollLockedSession()
at System.Web.SessionState.SessionStateModule.GetSessionStateItem()
at System.Web.SessionState.SessionStateModule.BeginAcquireState(Object source, EventArgs e, AsyncCallback cb, Object extraData)
Проходим по всей цепочке. В целом, ничего сильно подозрительного. Доходим до конечного метода:
private void QueueRef() {
if (!IsRequestQueueEnabled || _rqId == null) {
return;
}
//
// Check the limit
int count = 0;
s_queuedRequestsNumPerSession.TryGetValue(_rqId, out count);
if (count >= AppSettings.RequestQueueLimitPerSession) {
throw new HttpException(SR.GetString(SR.Request_Queue_Limit_Per_Session_Exceeded));
}
//
// Add ref
s_queuedRequestsNumPerSession.AddOrUpdate(_rqId, 1, (key, value) => value + 1);
}
Хм. А что это за настройка RequestQueueLimitPerSession которая упоминается почему-то только в .net 4.7, но при этом распространяется фиксами вплоть до .net 3.5?
Переходим для изучения настройки в AppSettings.cs. Бинго!
internal const int UnlimitedRequestsPerSession = Int32.MaxValue;
internal const int DefaultRequestQueueLimitPerSession = 50;
if (settings == null || !int.TryParse(settings["aspnet:RequestQueueLimitPerSession"], out _requestQueueLimitPerSession) || _requestQueueLimitPerSession < 0)
_requestQueueLimitPerSession = BinaryCompatibility.Current.TargetsAtLeastFramework463 ? DefaultRequestQueueLimitPerSession : UnlimitedRequestsPerSession;
Если у вас версия .net 4.6.3 (что по всей видимости есть системное название 4.6.2) или старше, то длина очереди на каждого пользователя оказывается обрублена 50 запросами, что достигается достаточно легко в определённых случаях использования. Увеличиваем лимит, заливаем, тестируем — happy end.
Надеюсь, что это статья на хабре поможет кому-нибудь, кто столкнется с той же проблемой, т.к. в интернете поиском информации по этой проблеме найти тяжело.
P.S.: Странно, что такие изменения идут в обход release notes. Видимо, дело именно в том, что это распространяли апдейтами и фиксами из 4.7. Но с моей точки зрения это явный breaking change, который на некоторое время частично свалил приложение и как-то более явно хотелось бы видеть такие изменения.
Автор: LbISS