Spaces:
Running
Running
SilverElixir commited on
Commit ·
8fdbe53
1
Parent(s): 85833be
Cap Telegram flood-wait sleep so long RetryAfter cannot hold the chat lock
Browse files- bot.py +6 -0
- docs/ENVIRONMENT.md +2 -0
- lumen_transport_calls.py +5 -2
bot.py
CHANGED
|
@@ -315,6 +315,12 @@ TELEGRAM_DOWNLOAD_MAX_BYTES = _env_number("TELEGRAM_DOWNLOAD_MAX_BYTES", 20 * 10
|
|
| 315 |
TELEGRAM_GET_FILE_TIMEOUT = _env_number("TELEGRAM_GET_FILE_TIMEOUT", 15, min_value=1)
|
| 316 |
# Cooldown после HTML-мусора от прокси вместо JSON: без него десятки вызовов/сек валят лавину WARNING (см. _tg_call).
|
| 317 |
TELEGRAM_PROXY_COOLDOWN_SEC = _env_number("TELEGRAM_PROXY_COOLDOWN_SEC", 20, min_value=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
# Лимит ожидания следующего куска для любого провайдера: зависший стрим иначе держит лок чата бесконечно.
|
| 319 |
STREAM_CHUNK_TIMEOUT_SEC = _env_number("STREAM_CHUNK_TIMEOUT_SEC", 30, min_value=1)
|
| 320 |
# ── Паттерн "живой печати" при стриминге (см. lumen_typing_pace.py) ──
|
|
|
|
| 315 |
TELEGRAM_GET_FILE_TIMEOUT = _env_number("TELEGRAM_GET_FILE_TIMEOUT", 15, min_value=1)
|
| 316 |
# Cooldown после HTML-мусора от прокси вместо JSON: без него десятки вызовов/сек валят лавину WARNING (см. _tg_call).
|
| 317 |
TELEGRAM_PROXY_COOLDOWN_SEC = _env_number("TELEGRAM_PROXY_COOLDOWN_SEC", 20, min_value=0)
|
| 318 |
+
# Потолок одного сна по Retry-After в _tg_call: длинный flood-wait иначе держит
|
| 319 |
+
# лок чата сверх бюджета маршрута (аудит 10.2026). Сам ретрай сохраняется.
|
| 320 |
+
TELEGRAM_FLOOD_SLEEP_MAX_SEC = _env_number("TELEGRAM_FLOOD_SLEEP_MAX_SEC", 10, min_value=1)
|
| 321 |
+
# TIKTOK_GROUP_REQUIRE_MENTION=1 — ссылки TikTok в группах без обращения к боту
|
| 322 |
+
# только пишутся в фон, а не качаются (аудит H2, 10.2026). По умолчанию выключено.
|
| 323 |
+
TIKTOK_GROUP_REQUIRE_MENTION = os.getenv("TIKTOK_GROUP_REQUIRE_MENTION", "0") == "1"
|
| 324 |
# Лимит ожидания следующего куска для любого провайдера: зависший стрим иначе держит лок чата бесконечно.
|
| 325 |
STREAM_CHUNK_TIMEOUT_SEC = _env_number("STREAM_CHUNK_TIMEOUT_SEC", 30, min_value=1)
|
| 326 |
# ── Паттерн "живой печати" при стриминге (см. lumen_typing_pace.py) ──
|
docs/ENVIRONMENT.md
CHANGED
|
@@ -66,6 +66,7 @@ Before publishing, configure the same `LUMEN_PROXY_SECRET` on HF and every Deno
|
|
| 66 |
| `TELEGRAM_MEDIA_TIMEOUT` | `25s` | Timeout for downloading media files from Telegram. |
|
| 67 |
| `TELEGRAM_DOWNLOAD_MAX_BYTES` | `20971520` (20 MB) | Cap on a single Telegram download (Bot API doesn't serve bigger files via `getFile`); bigger files are refused before downloading with an honest user-facing message. |
|
| 68 |
| `TELEGRAM_GET_FILE_TIMEOUT` | `15s` | Timeout for the `getFile` metadata call before a download. |
|
|
|
|
| 69 |
| `TTS_MAX_CHARS` | `800` | Size of one `/tts` synthesis chunk; up to `TTS_MAX_PARTS` (5) chunks are synthesized, so the accepted maximum is `TTS_MAX_CHARS × TTS_MAX_PARTS` (4000 by default). |
|
| 70 |
| `TTS_SYNTH_TIMEOUT_SEC` | `60s` | Timeout for a single TTS synthesis call. Also passed into the SDK as `http_options.timeout`, so a hung provider cannot hold the chat lock. |
|
| 71 |
| `TTS_TOTAL_BUDGET_SEC` | `240s` | Total deadline for all chunks of one multi-chunk TTS request; past it the text is cut and the user is told only the beginning was voiced. |
|
|
@@ -100,6 +101,7 @@ Before publishing, configure the same `LUMEN_PROXY_SECRET` on HF and every Deno
|
|
| 100 |
| `TIKTOK_SLIDESHOW_MAX_BYTES` | `200 MB` | Total RAM cap for one slideshow post; slides past it are skipped (a typical post is under 50 MB). |
|
| 101 |
| `TIKTOK_SLIDE_DOWNLOAD_CONCURRENCY` | `8` | Max slideshow slides downloaded in parallel; keeps one large post from hogging the shared HTTP connection pool. |
|
| 102 |
| `TIKTOK_VIDEO_SLIDE_PROBE_CONCURRENCY` | `4` | Max concurrent `ffprobe`/`ffmpeg` processes when probing "live" video slides in a slideshow. |
|
|
|
|
| 103 |
|
| 104 |
## Persistent storage
|
| 105 |
|
|
|
|
| 66 |
| `TELEGRAM_MEDIA_TIMEOUT` | `25s` | Timeout for downloading media files from Telegram. |
|
| 67 |
| `TELEGRAM_DOWNLOAD_MAX_BYTES` | `20971520` (20 MB) | Cap on a single Telegram download (Bot API doesn't serve bigger files via `getFile`); bigger files are refused before downloading with an honest user-facing message. |
|
| 68 |
| `TELEGRAM_GET_FILE_TIMEOUT` | `15s` | Timeout for the `getFile` metadata call before a download. |
|
| 69 |
+
| `TELEGRAM_FLOOD_SLEEP_MAX_SEC` | `10s` | Cap on a single `RetryAfter` sleep in Telegram calls, so a long flood-wait cannot hold the chat lock past the route budget (the retry itself is kept). |
|
| 70 |
| `TTS_MAX_CHARS` | `800` | Size of one `/tts` synthesis chunk; up to `TTS_MAX_PARTS` (5) chunks are synthesized, so the accepted maximum is `TTS_MAX_CHARS × TTS_MAX_PARTS` (4000 by default). |
|
| 71 |
| `TTS_SYNTH_TIMEOUT_SEC` | `60s` | Timeout for a single TTS synthesis call. Also passed into the SDK as `http_options.timeout`, so a hung provider cannot hold the chat lock. |
|
| 72 |
| `TTS_TOTAL_BUDGET_SEC` | `240s` | Total deadline for all chunks of one multi-chunk TTS request; past it the text is cut and the user is told only the beginning was voiced. |
|
|
|
|
| 101 |
| `TIKTOK_SLIDESHOW_MAX_BYTES` | `200 MB` | Total RAM cap for one slideshow post; slides past it are skipped (a typical post is under 50 MB). |
|
| 102 |
| `TIKTOK_SLIDE_DOWNLOAD_CONCURRENCY` | `8` | Max slideshow slides downloaded in parallel; keeps one large post from hogging the shared HTTP connection pool. |
|
| 103 |
| `TIKTOK_VIDEO_SLIDE_PROBE_CONCURRENCY` | `4` | Max concurrent `ffprobe`/`ffmpeg` processes when probing "live" video slides in a slideshow. |
|
| 104 |
+
| `TIKTOK_GROUP_REQUIRE_MENTION` | `0` | Set to `1` so TikTok links in groups without addressing the bot are only recorded as background instead of downloaded. |
|
| 105 |
|
| 106 |
## Persistent storage
|
| 107 |
|
lumen_transport_calls.py
CHANGED
|
@@ -219,7 +219,10 @@ async def _tg_call(method: Any, *args: Any, call_timeout: float | None = None, r
|
|
| 219 |
break
|
| 220 |
if attempt < retries:
|
| 221 |
# Флуд-контроль ждём по retry_after, остальное — коротким бэкоффом.
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
| 223 |
if last_exc is not None and "message is not modified" in str(last_exc).lower():
|
| 224 |
# "message is not modified" — валидный ответ API (семантический не-op), а не сбой: засчитываем успех, иначе повторные edit_text накручивали бы счётчик.
|
| 225 |
bot._tg_proxy_breaker.note_success()
|
|
@@ -250,7 +253,7 @@ async def telegram_api_call(method: str, payload: dict, *, request_timeout: floa
|
|
| 250 |
# См. _handle_proxy_failure — выключатель срабатывает по счётчику
|
| 251 |
# подряд идущих сбоев (см. _TelegramProxyCircuitBreaker), а не на первый же сбой.
|
| 252 |
await bot._handle_proxy_failure(f"вызове {method}")
|
| 253 |
-
# Цепочку не р
|
| 254 |
raise RuntimeError(f"Network error in telegram_api_call for {method}: {exc_str}") from exc
|
| 255 |
if not isinstance(data, dict) or not data.get("ok"):
|
| 256 |
# Прокси round-trip'нул нормально и вернул валидный JSON — сам факт, что
|
|
|
|
| 219 |
break
|
| 220 |
if attempt < retries:
|
| 221 |
# Флуд-контроль ждём по retry_after, остальное — коротким бэкоффом.
|
| 222 |
+
# Сон ограничен капом: длинный retry_after иначе держит лок чата
|
| 223 |
+
# сверх бюджета маршрута (аудит 10.2026). Ретрай после капа остаётся.
|
| 224 |
+
wait = _tg_retry_after_sec(exc) or 0.5 * (attempt + 1)
|
| 225 |
+
await asyncio.sleep(min(wait, bot.TELEGRAM_FLOOD_SLEEP_MAX_SEC))
|
| 226 |
if last_exc is not None and "message is not modified" in str(last_exc).lower():
|
| 227 |
# "message is not modified" — валидный ответ API (семантический не-op), а не сбой: засчитываем успех, иначе повторные edit_text накручивали бы счётчик.
|
| 228 |
bot._tg_proxy_breaker.note_success()
|
|
|
|
| 253 |
# См. _handle_proxy_failure — выключатель срабатывает по счётчику
|
| 254 |
# подряд идущих сбоев (см. _TelegramProxyCircuitBreaker), а не на первый же сбой.
|
| 255 |
await bot._handle_proxy_failure(f"вызове {method}")
|
| 256 |
+
# Цепочку сохраняем через from exc: Sentry должен видеть первопричину, а не только обёртку.
|
| 257 |
raise RuntimeError(f"Network error in telegram_api_call for {method}: {exc_str}") from exc
|
| 258 |
if not isinstance(data, dict) or not data.get("ok"):
|
| 259 |
# Прокси round-trip'нул нормально и вернул валидный JSON — сам факт, что
|