Инженеры Cloudflare потратили шесть недель на поиск почти невидимого бага в библиотеке hyper, написанной на Rust. Проблема проявлялась только для больших изображений и только в продакшене: запрос возвращал HTTP 200 с правильным Content-Length, но тело ответа приходило обрезанным. Вместо ожидаемых мегабайтов — пара сотен килобайт. Никаких ошибок в логах, никаких аварийных завершений.
Баг всплыл сразу после декабрьского редизайна Images binding. В Cloudflare поменяли архитектуру: убрали промежуточный сервис FL, который гонял трафик через сетевые сокеты, и заменили его на внутреннее связывание через Unix-сокеты на той же машине. Это ускорило обработку, но изменило, кто читает данные на другой стороне сокета. FL читал быстро, и буфер сокета почти никогда не заполнялся. Новый посредник иногда притормаживал — буквально на несколько миллисекунд. Этого хватило, чтобы проявился баг, который прятался годами.
Команда перепробовала всё: обновляла версии hyper (0.14, 1.7, 1.8 — ошибка воспроизводилась везде), проверяла таймауты, исключила Workers runtime, строила локальные репродукции на macOS и Debian. Но curl всегда работал идеально, а баг появлялся только под реальной нагрузкой. Решающий прорыв дал strace. Он показал, что в неудачных запросах сразу после первой порции заголовков и пары сотен килобайт данных вызывался shutdown на сокете. Остальные мегабайты так и оставались во внутреннем буфере hyper.
Корень — в цикле диспетчеризации HTTP/1 в dispatch.rs. Rust-код обрабатывал результат вызова poll_flush через let _. Это отбрасывает Poll::Pending — сигнал о том, что flush ещё не закончен. Если сокет переполнен, hyper думает, что всё отправлено, и закрывает соединение. Данные теряются. Исправление заняло четыре строки. В первоначальной версии инженеры добавили проверку результата poll_flush: если flush не завершён, цикл возвращает Poll::Pending и ждёт, пока сокет освободится. Потом они перенесли логику в точку вызова shutdown — перед закрытием сокета hyper теперь гарантированно сбрасывает остатки буфера.
Патч и детерминированный тест (TCP-обёртка, которая имитирует полный буфер) отправили в upstream через PR #4018. Исправление войдёт в будущий релиз hyper. Пока Cloudflare использует внутренний форк. После фикса Images binding стабилизировался, и недавно команда добавила поддержку операций для хостинговых изображений.