Reduce terminal chart update timeouts
This commit is contained in:
@@ -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"/,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user