Reduce terminal chart update timeouts

This commit is contained in:
2569718930@qq.com
2026-06-01 14:07:13 +08:00
parent 31fb1b3f28
commit ac5b512a72
6 changed files with 35 additions and 12 deletions
@@ -8,7 +8,7 @@ import {
const API_BASE = process.env.POLYWEATHER_API_BASE_URL;
const DETAIL_BATCH_PROXY_TIMEOUT_MS = Number(
process.env.POLYWEATHER_CITY_DETAIL_BATCH_PROXY_TIMEOUT_MS || "12000",
process.env.POLYWEATHER_CITY_DETAIL_BATCH_PROXY_TIMEOUT_MS || "15000",
);
export async function GET(req: NextRequest) {
@@ -258,7 +258,7 @@ export function runTests() {
"single-runway charts must not show the runway-detail toggle because aggregate and individual views are visually redundant",
);
assert(
chartLogic.includes("HOURLY_DETAIL_REQUEST_TIMEOUT_MS = 12_000") &&
chartLogic.includes("HOURLY_DETAIL_REQUEST_TIMEOUT_MS = 16_000") &&
chartLogic.includes("fetchCityDetailBatchWithTimeout") &&
chartLogic.includes("signal: controller.signal") &&
chartLogic.includes("controller.abort()"),
@@ -357,7 +357,7 @@ const HOURLY_FORCE_REFRESH_DEDUP_MS = 60_000;
const _hourlyCache = new Map<string, { ts: number; data: HourlyForecast }>();
const _hourlyRequestCache = new Map<string, Promise<HourlyForecast>>();
const MAX_HOURLY_DETAIL_CONCURRENT_REQUESTS = 3;
const HOURLY_DETAIL_REQUEST_TIMEOUT_MS = 12_000;
const HOURLY_DETAIL_REQUEST_TIMEOUT_MS = 16_000;
let _hourlyActiveDetailRequests = 0;
const _hourlyDetailRequestQueue: Array<() => void> = [];
const RUNWAY_LINE_COLORS = ["#00897b", "#d97706", "#7c3aed", "#0891b2", "#ea580c", "#64748b"];
@@ -41,6 +41,11 @@ export function runTests() {
const detailBatchProxy = readFrontend("app", "api", "cities", "detail-batch", "route.ts");
assert.match(detailBatchProxy, /createProxyTimer\(req,\s*"city_detail_batch"\)/);
assert.match(detailBatchProxy, /timing:\s*timer/);
assert.match(
detailBatchProxy,
/POLYWEATHER_CITY_DETAIL_BATCH_PROXY_TIMEOUT_MS\s*\|\|\s*"15000"/,
"city detail batch proxy should leave room for backend partial responses instead of aborting at the chart fetch deadline",
);
assert.match(
detailBatchProxy,
/fetchCache:\s*"no-store"/,
+14 -4
View File
@@ -853,13 +853,17 @@ def test_city_detail_batch_chart_scope_returns_only_chart_fields(monkeypatch):
assert "ai_analysis" not in detail
def test_chart_detail_payload_avoids_threadpool_queue_and_reuses_short_cache(monkeypatch):
def test_chart_detail_payload_uses_threadpool_and_reuses_short_cache(monkeypatch):
import asyncio
build_calls = 0
threadpool_calls = 0
async def fail_threadpool(*_args, **_kwargs):
raise AssertionError("chart payload should not wait behind the shared threadpool")
async def fake_run_in_threadpool(fn, *args, **kwargs):
nonlocal threadpool_calls
threadpool_calls += 1
await asyncio.sleep(0)
return fn(*args, **kwargs)
def build_chart_detail(data, resolution):
nonlocal build_calls
@@ -873,7 +877,7 @@ def test_chart_detail_payload_avoids_threadpool_queue_and_reuses_short_cache(mon
city_api._CITY_CHART_DETAIL_PAYLOAD_CACHE.clear()
city_api._CITY_CHART_DETAIL_PAYLOAD_CACHE_TS.clear()
monkeypatch.setenv("POLYWEATHER_CITY_DETAIL_PAYLOAD_CACHE_TTL_SEC", "20")
monkeypatch.setattr(city_api, "run_in_threadpool", fail_threadpool)
monkeypatch.setattr(city_api, "run_in_threadpool", fake_run_in_threadpool)
monkeypatch.setattr(city_api.legacy_routes, "_build_city_chart_detail_payload", build_chart_detail)
data = {
@@ -888,6 +892,12 @@ def test_chart_detail_payload_avoids_threadpool_queue_and_reuses_short_cache(mon
assert first == second
assert first["resolution"] == "10m"
assert build_calls == 1
assert threadpool_calls == 1
def test_city_detail_batch_partial_timeout_default_stays_below_proxy_budget(monkeypatch):
monkeypatch.delenv("POLYWEATHER_CITY_DETAIL_BATCH_PARTIAL_TIMEOUT_MS", raising=False)
assert city_api._city_detail_batch_partial_timeout_seconds() == 6.0
def test_city_detail_batch_endpoint_limits_backend_concurrency(monkeypatch):
+13 -5
View File
@@ -282,7 +282,11 @@ async def _build_city_chart_detail_payload(
) -> Dict[str, Any]:
ttl = _city_detail_payload_cache_ttl()
if ttl <= 0:
return legacy_routes._build_city_chart_detail_payload(data, resolution)
return await run_in_threadpool(
legacy_routes._build_city_chart_detail_payload,
data,
resolution,
)
key = _city_chart_detail_payload_cache_key(data, resolution)
now_ts = time.time()
@@ -292,7 +296,11 @@ async def _build_city_chart_detail_payload(
if cached is not None and now_ts - cached_ts < ttl:
return cached
payload = legacy_routes._build_city_chart_detail_payload(data, resolution)
payload = await run_in_threadpool(
legacy_routes._build_city_chart_detail_payload,
data,
resolution,
)
async with _CITY_CHART_DETAIL_PAYLOAD_LOCK:
_CITY_CHART_DETAIL_PAYLOAD_CACHE[key] = payload
@@ -709,11 +717,11 @@ def _city_detail_batch_concurrency() -> int:
def _city_detail_batch_partial_timeout_seconds() -> Optional[float]:
try:
timeout_ms = int(
os.getenv("POLYWEATHER_CITY_DETAIL_BATCH_PARTIAL_TIMEOUT_MS", "8500")
or "8500"
os.getenv("POLYWEATHER_CITY_DETAIL_BATCH_PARTIAL_TIMEOUT_MS", "6000")
or "6000"
)
except ValueError:
timeout_ms = 8500
timeout_ms = 6000
if timeout_ms <= 0:
return None
return max(0.001, min(60.0, timeout_ms / 1000.0))