Ваша LLM стримит в никуда: разбираемся, как работать с дисконнектами в FastAPI

В FastAPI клиентский дисконнект по умолчанию не останавливает выполнение обработчика, поэтому стриминг от большой языковой модели (LLM) может продолжаться «в пустоту». Главный итог: отключение нужно учитывать явно, иначе страдают ресурсы и корректность работы с БД.

  • По умолчанию разрыв соединения не прерывает пользовательский код: для обычных эндпоинтов событие http.disconnect игнорируется.
  • Для стриминга (StreamingResponse, EventSourceResponse) отключение ловится, но отмена может прервать транзакцию или код очистки.
  • В одном кейсе транзакции оставались открытыми до 40 минут (idle in transaction), после чего появились алерты о «мёртвых» соединениях в пуле.
  • Пример с нестриминговым эндпоинтом: были согласованы лимит параллельности 32 запроса и таймаут 30 секунд, но из-за бага у клиента таймаут стал 10 секунд и начались повторы.
  • В примерах используются версии: Uvicorn 0.40.0, Starlette 0.50.0, FastAPI 0.128.0, vLLM v0.13.0.
  • Нюанс ASGI: требование бросать OSError при send() появилось в ASGI 2.4, а Uvicorn 0.40.0 реализует 2.3 и при disconnected просто отбрасывает данные.

Почему это важно: В сервисах со стримингом ответов от LLM дисконнект клиента может привести к тому, что GPU продолжит генерировать токены, занимая ресурсы self-hosted моделей или накапливая расходы у внешних провайдеров. Ещё заметнее риск для базы данных: транзакции могут остаться незавершёнными, а в пул возвращаются соединения в невалидном состоянии. Без явной обработки теряется статистика незавершённых запросов, что мешает отладке и аналитике.

На что обратить внимание: В статье подчёркивается, что 100% гарантии обнаружения дисконнекта в рамках одного HTTP-запроса недостижимы, поэтому важен баланс между корректностью критичных фаз и возможностью прерывать долгие. Для нестриминговых ответов признак отключения появляется только при явном опросе request.receive() и проверке http.disconnect; метод Request.is_disconnected() описан как ненадёжный при BaseHTTPMiddleware. В стриминге ключевой сигнал — CancelledError, а риск связан с двумя конкурирующими путями завершения (например, ручной break плюс отмена таски фреймворком), из-за чего может пропускаться логика очистки.

Коротко

  • В сценариях «БД + долгое ожидание LLM» дисконнект обычно трактуется как повод отменить только долгую часть, сохранив корректное завершение критичной.
  • CancelledError в asyncio/anyio в статье выступает главным маркером отмены: вокруг него строятся логи, метрики и очистка ресурсов при обрыве.
  • Эффект дисконнектов часто проявляется косвенно: «брошенные» запросы продолжают выполняться, а симптомы видны по нагрузке, сессиям и состоянию пула.
  • Отдельно разбирается момент, когда request.receive() превращается в пассивное ожидание и начинает возвращать только событие http.disconnect.
  • Для SSE упоминается send_timeout: он помогает закрыть соединение, если клиент перестал читать данные и запись в TCP-буфер начинает блокироваться.

FAQ

Зачем вообще заморачиваться с обработкой дисконнектов в FastAPI, если клиент уже ушёл: какие потери ресурсов и риски для БД описаны в статье?

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

Что именно делает стек FastAPI/Starlette при разрыве соединения и чем различается поведение для обычных ответов и для StreamingResponse или EventSourceResponse?

Для нестриминговых Response дисконнект обычно игнорируется и код продолжает выполняться; в стриминге фреймворк мониторит http.disconnect и отменяет задачи, пробрасывая CancelledError.

Почему в статье предпочитают проверять дисконнект через await request.receive(), а не через Request.is_disconnected(), и в каких случаях это становится ненадёжным?

В тексте указано, что is_disconnected() может стабильно возвращать False при BaseHTTPMiddleware, тогда как receive() после чтения тела запроса получает только события http.disconnect.

Читайте также

  1. Возвращаем к жизни связку OpenClaw и Claude
  2. Как кодинг-агенты используют инструменты, память и контекст репозитория, чтобы писать код лучше
  3. Анализ документов нейросетью с цитатами из источников: скилл research-docs для Claude Code
  4. Контекстная амнезия: три агента, три IDE, ноль общей памяти
  5. Как я настроил OpenClaw для зоопарка лендингов своей компании
Ключевые инсайты из новости (по версии ChatGPT)
  • FastAPI: обычные эндпоинты не реагируют на client disconnect: В FastAPI/Starlette для нестриминговых ответов (Response/JSONResponse) событие http.disconnect само по себе не останавливает выполнение обработчика: код продолжает работать, даже если клиент уже закрыл соединение. Это важно учитывать в длительных операциях (CPU/GPU/внешние вызовы), иначе сервис тратит ресурсы на запросы, результат которых уже некому доставлять.
    [Backend / FastAPI / Надёжность]
Для получения полного доступа оформите подписку PubMag PRO.
Зарегистрированные пользователи видят только два тезиса.
Зарегистрироваться
Инсайты автоматически генерируются с помощью искусственного интеллекта на основе текста статьи.
← Назад в лентуЧитать оригинал →
✈️ Подписывайтесь на мой Telegram-канал — там еще больше интересного про AdTech, MarTech, AI и многое другое!