From 7e51223eb90425ac085ff15c9ba834dee0a789f5 Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Sat, 29 Aug 2026 10:00:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(rate-limit):=20=5Fis=5Frate=5Flimited=20?= =?UTF-8?q?=E8=AF=86=E5=88=AB=20RetryError=20=E7=9A=84=20429=20=E5=BD=A2?= =?UTF-8?q?=E6=80=81=EF=BC=8C=E7=99=BB=E5=BD=95=20429=20=E7=9C=9F=E6=AD=A3?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E8=87=AA=E5=8A=A8=E9=80=80=E9=81=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 生产实测:登录时 garth 把 429 折进 RetryError('too many 429 error responses'),无 status_code、无 'rate limit' 字样,旧检测漏判 → 走 else 分支存原始报错、不调 _note_rate_limit,冷却永远不落库,用户可反复撞 Garmin。\n\n- garmin.py: _is_rate_limited 沿 __cause__ 链找 429 响应,并接受文本中的 '429' 标记。\n- garmin_auth.py: 退避文案 6h 改为 24h。\n- 生产:已补挂 24h 冷却(2026-08-30 10:00 北京),RetryError 形状验证识别为 429。 --- backend/services/garmin.py | 18 ++++++++++++++++-- backend/services/garmin_auth.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/services/garmin.py b/backend/services/garmin.py index 74cf88b..f422ced 100644 --- a/backend/services/garmin.py +++ b/backend/services/garmin.py @@ -204,8 +204,22 @@ def _is_rate_limited(e): if response is not None and getattr(response, "status_code", None) == 429: return True - haystacks = [str(e), str(getattr(e, "doc", "") or "")] - return any("rate limit" in h.lower() for h in haystacks) + # The 429 can be buried several layers down: garth/urllib3 fold the final + # 429 into a RetryError whose message is 'too many 429 error responses' — + # no status code survives and the words "rate limit" never appear. Walk the + # cause chain and also accept the "429" marker itself. + seen = set() + cur = e + while cur is not None and id(cur) not in seen: + seen.add(id(cur)) + response = getattr(cur, "response", None) + if response is not None and getattr(response, "status_code", None) == 429: + return True + text = f"{cur} {getattr(cur, 'doc', '') or ''}" + if "429" in text or "rate limit" in text.lower(): + return True + cur = getattr(cur, "__cause__", None) or getattr(cur, "__context__", None) + return False class MFARequired(RuntimeError): diff --git a/backend/services/garmin_auth.py b/backend/services/garmin_auth.py index b91b853..91e47ec 100644 --- a/backend/services/garmin_auth.py +++ b/backend/services/garmin_auth.py @@ -121,7 +121,7 @@ def _run_login(session_id, user_id, garmin_email, password, is_cn, import_garmin if garmin_svc._is_rate_limited(e): garmin_svc._note_rate_limit(user_id) error = ( - "Garmin 返回 429 限流(登录接口)。已自动退避 6 小时," + "Garmin 返回 429 限流(登录接口)。已自动退避 24 小时," "请等待冷却窗口结束后再试——反复尝试会越撞越久。" ) else: