RequestQueueLimitPerSession и его распространение на старые версии .net

в 6:51, , рубрики: .net, .net framework 4.6, ASP.NET, C#, iis, session, метки:

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

Что может быть опасного в обновлении .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 сервис, поэтому следы слегка запутались. Появилось подозрение на то, что банально запросы стали приходить слишком часто и это перегружало очередь.

asmx

Да, у нас не всё на webApi2, по-прежнему есть некоторое количество 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js