После того как Cloudflare изменила внутреннюю архитектуру сервиса Cloudflare Images (переведя его с сетевых сокетов на локальные Unix-сокеты для ускорения работы), клиенты начали жаловаться, получая битые или недозагруженные картинки.
Обычные инструменты логирования на уровне приложения ничего не показывали — для них все выглядело идеально. Попытки воспроизвести баг локально через
curl тоже провалились. Команда инженеров искала баг 6 недель, а исправила всего четырьмя строчками кода. Прорыв случился, когда инженеры подключили низкоуровневую утилиту ядра
strace, чтобы отследить системные вызовы к сокетам. Оказалось, что при отправке больших файлов процесс делал один системный вызов записи в буфер сокета (sendto), а следующим шагом сразу же закрывал соединение на запись (shutdown), хотя большая часть данных еще оставалась во внутреннем буфере библиотеки.Виновником оказалась hyper — популярная open-source библиотека на Rust для работы с HTTP. Баг сидел в ней годами во множестве версий.
В цикле обработки соединений (
dispatch.rs) библиотека вызывала функцию сброса данных из буфера в сокет (poll_flush). Однако результат этого вызова просто игнорировался с помощью конструкции let _ = self.poll_flush(cx)?;. Если сокет на стороне получателя на пару миллисекунд заполнялся, функция возвращала статус «ожидания» (Poll::Pending), сообщая, что данные еще не ушли. Но из-за let _ библиотека игнорировала это, считала, что всё успешно отправлено, и преждевременно вызывала закрытие сокета, обрубая хвост файла.👈 Узнать детали в источнике
Когда вы проектируете межсервисное взаимодействие на сверхвысоких скоростях (особенно в K8s через sidecar-контейнеры, mesh-сети или локальные сокеты), всегда закладывайте тесты на переполнение буферов и симулируйте медленных клиентов.
@DevOpsKaz 😛
