Skip to content

fix(webhook): не терять фоновые задачи обработки обновлений - #187

Open
Fgeeha wants to merge 2 commits into
love-apples:mainfrom
Fgeeha:fix/webhook-background-tasks
Open

fix(webhook): не терять фоновые задачи обработки обновлений#187
Fgeeha wants to merge 2 commits into
love-apples:mainfrom
Fgeeha:fix/webhook-background-tasks

Conversation

@Fgeeha

@Fgeeha Fgeeha commented Aug 13, 2026

Copy link
Copy Markdown

Проблема

При use_create_task=True вебхук создаёт задачу и сразу забывает про неё
(maxapi/webhook/base.py):

if self.dp.use_create_task:
    asyncio.create_task(self.dp.handle(event_object))

asyncio.create_task не владеет задачей — event loop держит только слабую
ссылку. Пока задача выполняется без прерываний, всё работает, но на первом же
await внутри handle() задачу может забрать сборщик мусора, и обработка
обновления оборвётся без единой записи в логе. По этой же причине теряется и
исключение из хендлера: без вызова task.exception() Python ограничивается
предупреждением "Task exception was never retrieved" при сборке мусора.

В polling-ветке диспетчера то же самое место сделано правильно
(maxapi/dispatcher.py):

task = asyncio.create_task(self.handle(event))
self._background_tasks.add(task)
task.add_done_callback(self._on_background_task_done)

Дополнительно: у вебхука нет аналога stop_polling(), который дожидается
фоновых задач, поэтому при остановке приложения принятые обновления
обрываются вместе с event loop.

Это же ограничение — причина, по которой в pyproject.toml отключено правило
RUF006 («Store a reference to the return value of asyncio.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 — чисто.

При use_create_task=True метод _dispatch создавал задачу через
asyncio.create_task, не сохраняя ссылку на неё. Сборщик мусора мог забрать
задачу до завершения обработки события, а исключение внутри неё терялось —
в отличие от polling-ветки диспетчера, где задачи хранятся в пуле и
логируются.

Теперь вебхук держит собственный пул задач с тем же callback-логированием,
а интеграции дожидаются их при остановке приложения: lifespan у FastAPI,
on_cleanup у aiohttp, on_shutdown у Litestar.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread maxapi/webhook/base.py
self.dp = dp
self.bot = bot
self.secret = secret
self._background_tasks: set[asyncio.Task[Any]] = set()
Comment thread maxapi/webhook/base.py
Comment on lines +100 to +105
exc = task.exception()
if exc is not None:
logger_dp.exception(
"Необработанное исключение в фоновой задаче handle(): %r",
exc,
)
@Olegt0rr Olegt0rr added the enhancement New feature or request label Aug 13, 2026
logger.exception() вне except-блока берёт пустой sys.exc_info() и не
показывает traceback исключения из task.exception(). Заменено на
logger.error(..., exc_info=exc).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants