Skip to content

fix(bot): thread-based liveness watchdog for silent long-poll hangs#143

Merged
Time4Mind merged 1 commit into
mainfrom
fix/liveness-watchdog-vpn-rotation
Jul 7, 2026
Merged

fix(bot): thread-based liveness watchdog for silent long-poll hangs#143
Time4Mind merged 1 commit into
mainfrom
fix/liveness-watchdog-vpn-rotation

Conversation

@Time4Mind

Copy link
Copy Markdown
Owner

Summary

  • Both live instances (ccbot, ccbot2 on ogame-sina) went alive-but-deaf for ~30h starting 2026-07-06 00:00 UTC after the host's mandatory VDSina VPN-exit rotation swapped the egress proxy's upstream server mid-long-poll. Zero exceptions were ever raised, so the existing NetworkError/TimedOut self-exit (fix(bot): self-exit on sustained getUpdates network outage #135) never tripped — its detector only fires from inside PTB's own error handler, which requires an actual exception.
  • Adds a cheap async heartbeat (_heartbeat_loop) plus a watchdog running on its own OS thread (_liveness_watchdog_loop), independent of the event loop — necessary because if the loop itself is what's wedged, an asyncio-scheduled timeout would never fire either. After 90s of heartbeat silence it force-exits via the existing _terminate_for_sustained_conflict() path so Docker's restart: unless-stopped respawns a clean instance.
  • Confirmed live: both bots were silently deaf (no exception logged, no updates delivered — ccbot2 had 2 stuck /menu messages) despite the proxy path itself testing healthy again. Redeployed with this fix; both now hold their getUpdates poll again (verified via single-probe 409 Conflict).

Test plan

  • pytest -q -m "not integration" — 754 passed (both locally and in an ephemeral container against this branch's snapshot)
  • New tests/test_liveness_watchdog.py covers: fresh heartbeat tolerated, stale heartbeat triggers exit, just-under-budget tolerated, heartbeat loop actually advances the timestamp
  • Deployed to both ccbot and ccbot2 on ogame-sina; verified startup log shows "Liveness watchdog started"; verified both hold their long-poll post-deploy

🤖 Generated with Claude Code

The existing NetworkError/TimedOut self-exit (PR #135) only fires when
getUpdates actually raises. It doesn't always: on 2026-07-06 a VPN-pipeline
exit rotation (a mandatory background job that swaps the egress VDSina
server every few days) tore down the proxy's upstream mid-long-poll and
left the read wedged with zero further exceptions or log lines for ~30h on
both live instances. Add a cheap async heartbeat plus a watchdog on its own
OS thread (independent of the event loop, since a wedged loop can't fire
its own asyncio-scheduled timeout) that force-exits after 90s of silence
so the supervisor respawns a clean instance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Time4Mind Time4Mind merged commit dcc71dd into main Jul 7, 2026
4 checks passed
@Time4Mind Time4Mind deleted the fix/liveness-watchdog-vpn-rotation branch July 7, 2026 06:30
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