Preserve scan cache on timeout

This commit is contained in:
2569718930@qq.com
2026-05-31 23:54:35 +08:00
parent 3e5ff4ea8f
commit 22f4ac1f26
2 changed files with 104 additions and 0 deletions
+65
View File
@@ -2233,6 +2233,71 @@ def test_scan_terminal_service_returns_stale_payload_after_failed_refresh(monkey
assert stale["stale_reason"] == "upstream 504"
def test_scan_terminal_timeout_does_not_replace_better_cached_snapshot(monkeypatch):
import time
filters = {"scan_mode": "tradable", "limit": 5}
normalized_filters = scan_terminal_service._normalize_scan_terminal_filters(filters)
scan_terminal_cache._SCAN_TERMINAL_CACHE.clear()
previous_payload = {
"generated_at": "2026-05-31T00:00:00Z",
"snapshot_id": "scan-existing",
"filters": normalized_filters,
"summary": {"candidate_total": 2, "visible_count": 2},
"top_signal": {"id": "old-1"},
"rows": [{"id": "old-1"}, {"id": "old-2"}],
"status": "ready",
"stale": False,
"stale_reason": None,
"last_success_at": None,
"last_failed_at": None,
}
scan_terminal_cache.set_cached_scan_terminal_payload(
normalized_filters,
previous_payload,
)
monkeypatch.setattr(
scan_terminal_service,
"CITIES",
{"fast": {"tz": 0}, "slow": {"tz": 0}},
)
monkeypatch.setattr(scan_terminal_service, "SCAN_TERMINAL_BUILD_TIMEOUT_SEC", 0.01)
monkeypatch.setattr(scan_terminal_service, "SCAN_TERMINAL_MAX_WORKERS", 2)
def _scan_city(city_name, *_args, **_kwargs):
if city_name == "slow":
time.sleep(0.05)
return {
"city": city_name,
"candidate_total": 1,
"primary_scores": [80.0],
"rows": [
{
"id": f"{city_name}-row",
"market_key": f"{city_name}-market",
"edge_percent": 4.0,
"final_score": 80.0,
"volume": 1000,
}
],
}
monkeypatch.setattr(scan_terminal_service, "_scan_city_terminal_rows", _scan_city)
stale = scan_terminal_service.build_scan_terminal_payload(filters, force_refresh=True)
assert stale["status"] == "stale"
assert stale["stale"] is True
assert [row["id"] for row in stale["rows"]] == ["old-1", "old-2"]
assert stale["stale_reason"].startswith("scan terminal build timed out")
cached = scan_terminal_cache.get_cached_scan_terminal_payload(
normalized_filters,
ttl_sec=3600,
)
assert [row["id"] for row in cached["rows"]] == ["old-1", "old-2"]
def test_scan_terminal_service_returns_failed_without_success_snapshot(monkeypatch):
filters = {"scan_mode": "tradable", "limit": 5}
scan_terminal_cache._SCAN_TERMINAL_CACHE.clear()
+39
View File
@@ -47,6 +47,35 @@ def _normalize_city_key(value: Any) -> str:
return ALIASES.get(text, text)
def _rows_count(payload: Dict[str, Any]) -> int:
rows = payload.get("rows")
return len(rows) if isinstance(rows, list) else 0
def _build_stale_payload_for_timeout_if_better_cached(
*,
filters: Dict[str, Any],
cached_entry: Dict[str, Any],
ranked_rows: List[Dict[str, Any]],
timeout_message: Optional[str],
) -> Optional[Dict[str, Any]]:
success_payload = cached_entry.get("success_payload")
if not isinstance(success_payload, dict) or not success_payload.get("rows"):
return None
if _rows_count(success_payload) < len(ranked_rows):
return None
error_message = timeout_message or "市场扫描快照正在刷新中"
set_scan_terminal_failure_state(filters, error_message=error_message)
failed_entry = get_scan_terminal_cache_entry(filters) or cached_entry
return build_stale_scan_terminal_payload(
filters=filters,
success_payload=success_payload,
error_message=error_message,
failed_at=failed_entry.get("last_failed_at"),
)
def _start_scan_terminal_background_refresh(filters: Dict[str, Any]) -> bool:
if not mark_scan_terminal_refreshing(filters):
return False
@@ -189,6 +218,16 @@ def _build_scan_terminal_payload_uncached(
summary = ranked_result["summary"]
top_signal = ranked_result["top_signal"]
if timed_out:
stale_payload = _build_stale_payload_for_timeout_if_better_cached(
filters=filters,
cached_entry=cached_entry,
ranked_rows=ranked_rows,
timeout_message=timeout_message,
)
if stale_payload is not None:
return stale_payload
payload = {
"generated_at": datetime.utcnow().isoformat() + "Z",
"filters": filters,