From e956e3a6d77a6bad3a7d6e0b9e74dedb2435319f Mon Sep 17 00:00:00 2001 From: kevin9327 <5299031+kevin9327@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:36:30 +0900 Subject: [PATCH] =?UTF-8?q?fix(trading-day):=20=E3=80=8C=E6=9C=AA=E7=9F=A5?= =?UTF-8?q?=E3=80=8D=E7=BB=93=E8=AE=BA=E4=B9=9F=E6=8C=89=20TTL=20=E7=BC=93?= =?UTF-8?q?=E5=AD=98,=20=E4=B8=8D=E5=86=8D=E6=AF=8F=E6=8B=8D=E9=87=8D?= =?UTF-8?q?=E6=89=93=E6=8E=A2=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit is_trading_day 的缓存命中条件带了 `_CACHE.verdict is not None`, 于是探测链 全部返回 None 时缓存永远读不回来, _TTL_UNKNOWN_S(300s) 形同虚设。 消费方 quote_service._holiday_gate 与 minute_refresh._gate_reason 在轮询循环里 每拍都调 is_trading_day: 未配 fuyao 且 tickflow 实时不可用/网络失败时, 每拍都会 重跑一遍探测链 (含一次 quotes.get 网络请求), 白白消耗限流额度并刷日志。 _CACHE.day 只在探测写回时设置, 「当天已探过」用它判定即可, 去掉多余的 verdict 判空条件。已有的 test_unknown_verdict_retries_after_short_ttl 仍然通过 (TTL 过期后照常重探)。 --- backend/app/services/trading_day.py | 4 +++- backend/tests/test_trading_day.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/app/services/trading_day.py b/backend/app/services/trading_day.py index bfca3f9..8079241 100644 --- a/backend/app/services/trading_day.py +++ b/backend/app/services/trading_day.py @@ -113,9 +113,11 @@ def is_trading_day(now: datetime | None = None) -> bool | None: return False with _CACHE_LOCK: + # 「未知」(None) 也是一个结论, 同样按 TTL 缓存 —— 它正是 _TTL_UNKNOWN_S 要 + # 挡住的场景 (未配 fuyao 且 tickflow 不可用时, 轮询每拍都会重打一次探测)。 + # _CACHE.day 只在探测写回时设置, 因此「当天已探过」用它判定即可。 if ( _CACHE.day == now.date() - and _CACHE.verdict is not None and (time.monotonic() - _CACHE.probed_at) < _ttl_of(_CACHE.verdict) ): return _CACHE.verdict diff --git a/backend/tests/test_trading_day.py b/backend/tests/test_trading_day.py index 586c8a0..01508d9 100644 --- a/backend/tests/test_trading_day.py +++ b/backend/tests/test_trading_day.py @@ -254,3 +254,25 @@ def test_fuyao_provider_trading_days_conversion(monkeypatch): monkeypatch.setattr(fp, "get_api_key", lambda: "test-key") days = FuyaoProvider().trading_days() assert days == {date(2026, 9, 4), date(2026, 9, 7)} + + +def test_unknown_verdict_is_cached_within_short_ttl(monkeypatch): + """未知结论也要按 _TTL_UNKNOWN_S 缓存: 轮询每拍重探会重复打 tickflow 请求。""" + monday = datetime(2026, 9, 7, 10, 0, tzinfo=CN) + calls = {"fuyao": 0, "tickflow": 0} + + def _fuyao(now): + calls["fuyao"] += 1 + return None + + def _tickflow(now): + calls["tickflow"] += 1 + return None + + monkeypatch.setattr(trading_day, "_probe_fuyao", _fuyao) + monkeypatch.setattr(trading_day, "_probe_tickflow", _tickflow) + + assert is_trading_day(monday) is None + assert is_trading_day(monday) is None + assert is_trading_day(monday) is None + assert calls == {"fuyao": 1, "tickflow": 1}