Skip to content

fix: prevent silent polling death (zombie bot) — timeouts, bootstrap retries, liveness watchdog - #108

Closed
mondominator wants to merge 1 commit into
toddrob99:mainfrom
mondominator:fix/never-zombie
Closed

mondominator wants to merge 1 commit into
toddrob99:mainfrom
mondominator:fix/never-zombie

Conversation

@mondominator

Copy link
Copy Markdown

Problem

The bot can end up in a zombie state: the getUpdates polling loop dies, but the process stays alive — so restart: always never fires and the bot silently stops responding until someone notices and manually restarts the container. I hit this twice in production:

  1. Boot-time network failure: container started before DNS was ready → telegram.error.NetworkError: httpx.ConnectError: [Errno -3] Temporary failure in name resolution → bot dead on arrival. run_polling() uses bootstrap_retries=0 by default, so a single startup network error is fatal.
  2. Mid-flight stall: a Telegram Bad Gateway storm (5 errors logged through the error handler in ~20s), after which polling went silent for 2.5 hours with the process still running. With no explicit get_updates_* timeouts, a silently dropped long-poll connection can stall indefinitely.

In both cases docker ps shows the container healthy and Up, while the bot is unresponsive in Telegram.

Fix (three layers)

  1. Explicit getUpdates timeouts (connect 10s / read 30s / write 10s / pool 10s) — a dropped connection now surfaces as an error PTB can retry, instead of hanging the polling task forever.
  2. run_polling(bootstrap_retries=-1) — startup network failures retry indefinitely instead of killing the bot (containers frequently race DNS/network at boot).
  3. Liveness watchdog + fail-fast — a daemon thread checks application.running / updater.running every 60s (3-minute startup grace); after 3 consecutive failures it logs CRITICAL and exits 75 so the container restart policy can recover. Unhandled exceptions escaping run_polling() now sys.exit(1) for the same reason. A crashed bot that restarts in seconds beats a zombie that's dead for days.

Notes

  • Verified against python-telegram-bot 22.7 (the version installed by requirements) — bootstrap_retries default confirmed 0, all get_updates_* builder methods present.
  • No behavior change for healthy operation; dev mode (-d) behavior unchanged.
  • Running in production since 2026-07-13 — clean startup, stable polling.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XzUNvRCXML3DJe98KBCGkN

…retries, liveness watchdog

Three defects made the bot die silently while the process stayed alive
(restart: always never fired):

1. getUpdates long-poll had no explicit timeouts — a silently dropped
   connection stalled polling forever (observed: Bad Gateway storm,
   then hours of zombie).
2. bootstrap_retries defaulted to 0 — a boot-time DNS failure killed
   the bot instead of retrying.
3. Nothing converted a dead polling loop into a process exit.

Fixes: get_updates_* timeouts (10/30/10/10), run_polling(bootstrap_retries=-1),
liveness watchdog thread that exits 75 after 3 failed checks so the
container restart policy recovers, and sys.exit(1) on unhandled exceptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzUNvRCXML3DJe98KBCGkN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant