История Одной Ошибки В .Net 5

Как мы столкнулись с неожиданной ошибкой в .

NET 5, исследовали проблему и что из этого вышло.



История одной ошибки в .
</p><p>
NET 5

В один прекрасный день было принято решение о переносе рабочего проекта с .

NET Core 3.1 на .

NET 5. Миграция оказалась проще, чем, например, при переходе с .

NET Core 2.1 на .

NET Core 3 из-за меньшего количества модификации.

По сути, речь шла всего лишь о смене TargetFramework на net5.0, обновлении нескольких библиотек и исправлении пары мест в коде, которые стали устаревшими, чтобы это не было так больно делать в будущем.

Команда тестирования проверила обновление вместе с другими задачами, а затем релиз был запущен в производство.

Но счастье было недолгим.

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

Логи показали, что несколько минут после выкатки релиза все работало отлично, но затем HTTPS-запросы к шине одного из клиентов почему-то стали проваливаться с ошибкой авторизации (401).

Мы выкатили релиз еще раз, но это не помогло — проблема повторилась.

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

NET Core 3.1, а затем начать разбираться с проблемой в фоновом режиме, чтобы потом перейти на .

NET 5.



Пусть расследование начнется

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

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

Все запросы были выполнены успешно, что довольно странно.

Возможно, что-то изменилось на стороне сервера, куда отправляются запросы? На всякий случай аналогичные манипуляции я провожу с помощью Curl, консольных приложений на Python и Go. Но в данном случае проблема не воспроизводится.

Что еще может отличаться при производстве и при локальном воспроизведении? Разработка ведется на Windows, а производство — на Linux (опущу вопрос, почему при разработке не используется хотя бы Docker).

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

Но на .

NET Core 3.1 все работало, может еще что-то поменяли? Я запускаю то же консольное приложение, но меняю TargetFramework на netcoreapp3.1, и все работает как в Windows, так и в Linux. Возникает вопрос, почему в продакшене ошибки начинаются не с первого запроса, а только через несколько минут? Ответ прост: используется HttpClientFactory , который кэширует HttpClientHandler для клиентов на некоторое время и повторно использует его для запросов, а когда приходит время создавать новый обработчик, вот тут всё и ломается.

Отлично.

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

Но причина ошибки пока остаётся неизвестной, и искать её в продакшене не очень хочется.

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



История одной ошибки в .
</p><p>
NET 5

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

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

Кстати, на последней macOS все пять запросов заканчиваются ошибкой.

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

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

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

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

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

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



Ищем ошибку



Внешняя отладка

Для повседневной разработки я использую Jetbrains Rider. Имеет поддержку так называемой внешней отладки (External source debug).

В этом режиме отладчик может покопаться в недрах .

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



История одной ошибки в .
</p><p>
NET 5

Поскольку ошибку невозможно воспроизвести в Windows, отладку необходимо проводить в среде Linux, чего можно добиться двумя способами:

  1. Установите Linux на свой рабочий компьютер;
  2. Воспользуйтесь преимуществом ВСЛ 2 или Докер.

Во втором случае вам придется подключать отладчик к работающему процессу по SSH, поскольку поддержка присутствует в обеих IDE. Однако в Rider почему-то при таком способе подключения отключена внешняя отладка, поэтому для поиска ошибки придется использовать Visual Studio, имеющую аналогичный функционал.

В результате отладки обнаружены потенциальные жертвы: классы безопасный канал И SslStreamCertificateContext .

Последний имеет разные реализации для разных операционных систем, что достигается с помощью условной компиляции соответствующих частичных классов: в Windows используется нативный модуль для работы с сертификатами, а в Linux — OpenSSL. После выполнения первого запроса сеанс SSL кэшируется в SslSessionsCache. При проблемном запросе сессия повторно используется из кеша, и по какой-то причине отправляется меньший массив данных, чем при обычном запросе.

Если вы используете отражение для очистки кэша сеанса SSL перед проблемным вызовом, HTTP-запрос будет успешно завершен:

   

var assembly = AppDomain.CurrentDomain.GetAssemblies()

Теги: #Сетевые технологии #github #программирование #Go #C++ #.

NET #Visual Studio #отладка #SSL #миграция проекта #.

net 5.0 #сертификаты x.509 #bugfix

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.