fix(telegram): back off on transient polling failures
Give getUpdates a wider HTTP timeout than the long-poll window, log transient SSL/network disconnects as warnings, apply exponential backoff with recovery logging, and only escalate to error after five consecutive failures.
This commit is contained in:
1 parent
2d42d8ed55
commit
71a0e8c1d6
3 files changed
+96
-7
No files matched your search
@@ -86,9 +86,17 @@ class TelegramClient:
|
||||
self.poll_timeout = poll_timeout
|
||||
self.logger = logger
|
||||
|
||||
def _call(self, method: str, params: Mapping[str, Any] | None = None) -> Any:
|
||||
def _call(
|
||||
self,
|
||||
method: str,
|
||||
params: Mapping[str, Any] | None = None,
|
||||
*,
|
||||
timeout: float | None = None,
|
||||
) -> Any:
|
||||
url = f"https://api.telegram.org/bot{self.token}/{method}"
|
||||
payload = self.transport.post(url, params or {}, timeout=self.timeout)
|
||||
payload = self.transport.post(
|
||||
url, params or {}, timeout=self.timeout if timeout is None else timeout
|
||||
)
|
||||
if not isinstance(payload, Mapping):
|
||||
raise TelegramError("Telegram 响应不是对象")
|
||||
if not payload.get("ok"):
|
||||
@@ -145,7 +153,10 @@ class TelegramClient:
|
||||
}
|
||||
if offset is not None:
|
||||
params["offset"] = int(offset)
|
||||
result = self._call("getUpdates", params)
|
||||
# 长轮询本身会挂起 poll_timeout 秒,HTTP 读超时必须留出更大余量。
|
||||
result = self._call(
|
||||
"getUpdates", params, timeout=self.poll_timeout + 15
|
||||
)
|
||||
return result if isinstance(result, list) else []
|
||||
|
||||
def answer_callback(self, callback_id: str, text: str = "") -> Any:
|
||||
|
||||
@@ -88,11 +88,41 @@ class TelegramPoller:
|
||||
self._log("warning", "telegram.answer_failed", error=str(exc))
|
||||
return False
|
||||
|
||||
def run_forever(self, stop_event=None) -> None:
|
||||
def run_forever(
|
||||
self,
|
||||
stop_event=None,
|
||||
*,
|
||||
initial_delay: float = 3.0,
|
||||
max_delay: float = 60.0,
|
||||
max_iterations: int | None = None,
|
||||
) -> None:
|
||||
offset: int | None = None
|
||||
failures = 0
|
||||
delay = initial_delay
|
||||
iterations = 0
|
||||
while stop_event is None or not stop_event.is_set():
|
||||
try:
|
||||
offset = self.run_once(offset)
|
||||
except Exception as exc: # noqa: BLE001 长轮询失败后退避重试
|
||||
self._log("error", "telegram.poll_failed", error=str(exc))
|
||||
self._sleep(3)
|
||||
except Exception as exc: # noqa: BLE001 长轮询瞬时断连后退避重试
|
||||
failures += 1
|
||||
level = "error" if failures >= 5 else "warning"
|
||||
self._log(
|
||||
level, "telegram.poll_failed",
|
||||
error=str(exc), failures=failures, retry_in=delay,
|
||||
)
|
||||
if stop_event is not None:
|
||||
if stop_event.wait(delay):
|
||||
break
|
||||
else:
|
||||
self._sleep(delay)
|
||||
delay = min(delay * 2, max_delay)
|
||||
else:
|
||||
if failures:
|
||||
self._log(
|
||||
"info", "telegram.poll_recovered", failures=failures
|
||||
)
|
||||
failures = 0
|
||||
delay = initial_delay
|
||||
iterations += 1
|
||||
if max_iterations is not None and iterations >= max_iterations:
|
||||
break
|
||||
Reference in new issue
Block a user