fix(llm): cap серверного Retry-After через _MAX_BACKOFF_S=30s (#1209) #1261

Merged
bot-backend merged 1 commit from fix/llm-client-retry-after-cap into main 2026-06-13 06:07:47 +00:00
Collaborator

Summary

Closes #1209 (P2 performance / availability). Серверный Retry-After не капался → time.sleep мог блокировать anyio-threadpool на часы → исчерпание пула → стопор приложения.

Bug

client.py:215:

wait = e.retry_after if e.retry_after is not None else min(2**attempt, 30)
time.sleep(wait)

Cap min(...,30) применён ТОЛЬКО к exponential backoff. Серверный Retry-After уходит как есть. Provider'ы шлют до 86400с при quota-exhaustion (OpenAI, CDN-503).

Эффект: time.sleep блокирует поток anyio-threadpool на часы (до 48ч при llm_max_retries=2). Chat-консьюмер (chat.py) мостится через run_in_threadpool — поток держит токен пула (~40, общий с sync Depends(get_db)) и DB-сессию → пул исчерпывается → стопор приложения.

Fix

  • Module-level константа _MAX_BACKOFF_S = 30.0.
  • Применяется к обеим веткам (Retry-After И exp.backoff).
  • raw_wait логируется отдельно (наблюдаемость — видно когда провайдер просил больше).

Test plan

  • Новый тест test_rate_limited_retry_after_capped: provider шлёт retry_after=86400time.sleep вызывается с 30, не 86400.
  • pytest tests/services/llm/test_client.py14 passed.
  • pytest tests/services/llm/55 passed.
  • ruff check → clean.

Closes #1209

## Summary Closes #1209 (P2 performance / availability). Серверный `Retry-After` не капался → `time.sleep` мог блокировать `anyio-threadpool` на часы → исчерпание пула → стопор приложения. ## Bug `client.py:215`: ```python wait = e.retry_after if e.retry_after is not None else min(2**attempt, 30) time.sleep(wait) ``` Cap `min(...,30)` применён ТОЛЬКО к exponential backoff. Серверный `Retry-After` уходит как есть. Provider'ы шлют до **86400с** при quota-exhaustion (OpenAI, CDN-503). Эффект: `time.sleep` блокирует поток `anyio-threadpool` на часы (до 48ч при `llm_max_retries=2`). Chat-консьюмер (`chat.py`) мостится через `run_in_threadpool` — поток держит токен пула (~40, общий с `sync Depends(get_db)`) и DB-сессию → пул исчерпывается → **стопор приложения**. ## Fix - Module-level константа `_MAX_BACKOFF_S = 30.0`. - Применяется к **обеим** веткам (Retry-After И exp.backoff). - `raw_wait` логируется отдельно (наблюдаемость — видно когда провайдер просил больше). ## Test plan - [x] Новый тест `test_rate_limited_retry_after_capped`: provider шлёт `retry_after=86400` → `time.sleep` вызывается с **30**, не 86400. - [x] `pytest tests/services/llm/test_client.py` → **14 passed**. - [x] `pytest tests/services/llm/` → **55 passed**. - [x] `ruff check` → clean. Closes #1209
bot-backend added 1 commit 2026-06-13 05:59:28 +00:00
fix(llm): cap серверного Retry-After через _MAX_BACKOFF_S=30s (#1209)
All checks were successful
CI / changes (push) Successful in 8s
CI / frontend-tests (push) Has been skipped
CI / changes (pull_request) Successful in 9s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m42s
CI / backend-tests (pull_request) Successful in 6m36s
dd773d6bde
В complete-loop'е min(...,30) применялся ТОЛЬКО к exponential backoff'у
(else-ветка). Серверный Retry-After уходил в time.sleep как есть.
_parse_retry_after принимает любое число секунд ("86400".isdigit() → True),
а provider'ы шлют Retry-After до 86400с при quota-exhaustion (OpenAI,
CDN-503). Эффект: time.sleep блокирует поток anyio-threadpool на часы
(до 48ч при llm_max_retries=2). Async-консьюмер (chat.py) мостится через
run_in_threadpool — поток держит токен пула (~40 потоков, общий с sync
Depends(get_db)) и DB-сессию → пул исчерпывается → стопор приложения.

Patch: вынес кап на module-level _MAX_BACKOFF_S=30s, применяю к ОБЕИМ
веткам (Retry-After И exp.backoff). raw_wait логируется отдельно для
наблюдаемости (видно когда провайдер просил больше).

Новый юнит-тест test_rate_limited_retry_after_capped: provider шлёт
retry_after=86400 → time.sleep вызывается с 30 (не 86400). 14/14
client тестов + 55/55 LLM-suite зелёные. ruff clean.

Closes #1209
bot-backend merged commit c29f4aef57 into main 2026-06-13 06:07:47 +00:00
bot-backend deleted branch fix/llm-client-retry-after-cap 2026-06-13 06:07:47 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1261
No description provided.