fix(webhook): не терять фоновые задачи обработки обновлений - #187
Open
Fgeeha wants to merge 2 commits into
Open
Conversation
При use_create_task=True метод _dispatch создавал задачу через asyncio.create_task, не сохраняя ссылку на неё. Сборщик мусора мог забрать задачу до завершения обработки события, а исключение внутри неё терялось — в отличие от polling-ветки диспетчера, где задачи хранятся в пуле и логируются. Теперь вебхук держит собственный пул задач с тем же callback-логированием, а интеграции дожидаются их при остановке приложения: lifespan у FastAPI, on_cleanup у aiohttp, on_shutdown у Litestar.
Contributor
There was a problem hiding this comment.
Pull request overview
Исправление webhook-интеграций, чтобы при use_create_task=True фоновые задачи обработки обновлений не терялись сборщиком мусора и могли быть корректно дожданы при остановке приложения (по аналогии с polling-веткой Dispatcher).
Changes:
BaseMaxWebhookначал хранить созданныеasyncio.Taskв пуле и удалять их по завершении через done-callback.- Добавлен
_shutdown()и подключён к lifecycle-хукам FastAPI (lifespan), aiohttp (on_cleanup) и Litestar (on_shutdown). - Добавлены тесты, проверяющие удержание ссылки на задачу, ожидание завершения и логирование исключений.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| maxapi/webhook/base.py | Добавлен пул фоновых задач, done-callback и _shutdown() для ожидания задач при остановке. |
| maxapi/webhook/fastapi.py | Lifespan оборачивает yield в try/finally и вызывает _shutdown() при остановке. |
| maxapi/webhook/aiohttp.py | Добавлен on_shutdown() и подключение к app.on_cleanup. |
| maxapi/webhook/litestar.py | Добавлен on_shutdown() и регистрация on_shutdown при create_app(). |
| tests/test_webhook/test_background_tasks.py | Новые тесты поведения фоновых задач и _shutdown() для webhook. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| self.dp = dp | ||
| self.bot = bot | ||
| self.secret = secret | ||
| self._background_tasks: set[asyncio.Task[Any]] = set() |
Comment on lines
+100
to
+105
| exc = task.exception() | ||
| if exc is not None: | ||
| logger_dp.exception( | ||
| "Необработанное исключение в фоновой задаче handle(): %r", | ||
| exc, | ||
| ) |
logger.exception() вне except-блока берёт пустой sys.exc_info() и не показывает traceback исключения из task.exception(). Заменено на logger.error(..., exc_info=exc).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Проблема
При
use_create_task=Trueвебхук создаёт задачу и сразу забывает про неё(
maxapi/webhook/base.py):asyncio.create_taskне владеет задачей — event loop держит только слабуюссылку. Пока задача выполняется без прерываний, всё работает, но на первом же
awaitвнутриhandle()задачу может забрать сборщик мусора, и обработкаобновления оборвётся без единой записи в логе. По этой же причине теряется и
исключение из хендлера: без вызова
task.exception()Python ограничиваетсяпредупреждением "Task exception was never retrieved" при сборке мусора.
В polling-ветке диспетчера то же самое место сделано правильно
(
maxapi/dispatcher.py):Дополнительно: у вебхука нет аналога
stop_polling(), который дожидаетсяфоновых задач, поэтому при остановке приложения принятые обновления
обрываются вместе с event loop.
Это же ограничение — причина, по которой в
pyproject.tomlотключено правилоRUF006(«Store a reference to the return value ofasyncio.create_task») спометкой «требует рефакторинга». Патч закрывает случай в вебхуке; ещё два
места (
connection/base.pyи polling-ветка) остаются, поэтому правило пока невключаю.
Решение
BaseMaxWebhookдержит собственный пул задач и вешает done-callback с темже логированием исключений, что и диспетчер;
_shutdown(), который дожидается принятых обновлений;lifespanу FastAPI,on_cleanupу aiohttp,on_shutdownу Litestar. У aiohttp и Litestarпубличный хук
on_shutdownпарный к существующемуon_startup, так чторучная сборка приложения выглядит симметрично.
Поведение при
use_create_task=False(значение по умолчанию) не меняется.Тесты
tests/test_webhook/test_background_tasks.py— 4 теста:gc.collect(), пока обработка идёт;_shutdown()дожидается всех принятых обновлений;use_create_task=Falseобработка по-прежнему идёт внутри запроса.Локально:
pytest -q— 958 passed, 12 skipped;mypy maxapi— чисто;ruff format --check— чисто.