fix: Telegram 409 감지 시 백오프 대신 polling 즉시 종료 (#180)
Some checks failed
CI / test (pull_request) Has been cancelled

409 충돌 감지 시 30초 백오프 후 재시도하는 방식에서
_running = False로 polling을 즉시 중단하는 방식으로 변경.

다중 인스턴스가 실행 중인 경우 재시도는 의미 없고 충돌만 반복됨.
이제 409 발생 시 이 프로세스의 Telegram 명령어 polling을 완전히 비활성화.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
agentson
2026-02-20 09:35:33 +09:00
parent 77577f3f4d
commit aceba86186
2 changed files with 23 additions and 37 deletions

View File

@@ -877,10 +877,11 @@ class TestGetUpdates:
assert updates == []
@pytest.mark.asyncio
async def test_get_updates_409_sets_conflict_backoff(self) -> None:
"""409 Conflict response sets conflict_backoff_until and returns empty list."""
async def test_get_updates_409_stops_polling(self) -> None:
"""409 Conflict response stops the poller (_running = False) and returns empty list."""
client = TelegramClient(bot_token="123:abc", chat_id="456", enabled=True)
handler = TelegramCommandHandler(client)
handler._running = True # simulate active poller
mock_resp = AsyncMock()
mock_resp.status = 409
@@ -894,40 +895,34 @@ class TestGetUpdates:
updates = await handler._get_updates()
assert updates == []
assert handler._conflict_backoff_until > 0 # backoff was set
assert handler._running is False # poller stopped
@pytest.mark.asyncio
async def test_poll_loop_skips_during_conflict_backoff(self) -> None:
"""_poll_loop skips _get_updates while conflict backoff is active."""
async def test_poll_loop_exits_after_409(self) -> None:
"""_poll_loop exits naturally after _running is set to False by a 409 response."""
import asyncio as _asyncio
client = TelegramClient(bot_token="123:abc", chat_id="456", enabled=True)
handler = TelegramCommandHandler(client)
# Set an active backoff (far in the future)
handler._conflict_backoff_until = _asyncio.get_event_loop().time() + 600
call_count = 0
get_updates_called = []
async def mock_get_updates() -> list[dict]:
get_updates_called.append(True)
async def mock_get_updates_409() -> list[dict]:
nonlocal call_count
call_count += 1
# Simulate 409 stopping the poller
handler._running = False
return []
handler._get_updates = mock_get_updates # type: ignore[method-assign]
handler._get_updates = mock_get_updates_409 # type: ignore[method-assign]
# Run one iteration of the poll loop then stop
handler._running = True
task = _asyncio.create_task(handler._poll_loop())
await _asyncio.sleep(0.05)
handler._running = False
task.cancel()
try:
await task
except _asyncio.CancelledError:
pass
await _asyncio.wait_for(task, timeout=2.0)
# _get_updates should NOT have been called while backoff is active
assert get_updates_called == []
# _get_updates called exactly once, then loop exited
assert call_count == 1
assert handler._running is False
class TestCommandWithArgs: