Queue stale weather refreshes from user paths
This commit is contained in:
@@ -10,6 +10,8 @@ from typing import Any, Callable, Iterable, List, Mapping, Optional, Sequence
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from src.database.db_manager import DBManager
|
||||
|
||||
|
||||
ASIA_CORE_CITIES = (
|
||||
"hong kong",
|
||||
@@ -62,6 +64,7 @@ SOURCE_PRIORITY_WEIGHT = {
|
||||
"metar": 6,
|
||||
"wunderground": 4,
|
||||
}
|
||||
_CACHE_WARMER_DB = DBManager()
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool) -> bool:
|
||||
@@ -257,16 +260,25 @@ class CacheWarmer:
|
||||
return completed
|
||||
|
||||
|
||||
def _queue_city_panel_refresh(city: str, *, force_refresh: bool = False) -> bool:
|
||||
_CACHE_WARMER_DB.enqueue_observation_refresh_request(
|
||||
city=city,
|
||||
kind="panel",
|
||||
priority="normal",
|
||||
reason="cache_warmer",
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def build_default_cache_warmer() -> CacheWarmer:
|
||||
from web.core import CITIES
|
||||
from web.scan_terminal_service import build_scan_terminal_payload
|
||||
from web.services.city_runtime import _refresh_city_panel_cache
|
||||
|
||||
hot_cities = _parse_city_list(os.getenv("POLYWEATHER_WARMER_HOT_CITIES"))
|
||||
return CacheWarmer(
|
||||
city_provider=lambda: CITIES,
|
||||
scan_warmer=build_scan_terminal_payload,
|
||||
city_panel_warmer=_refresh_city_panel_cache,
|
||||
city_panel_warmer=_queue_city_panel_refresh,
|
||||
scan_interval_sec=_env_int("POLYWEATHER_WARMER_SCAN_INTERVAL_SEC", 300),
|
||||
city_interval_sec=_env_int("POLYWEATHER_WARMER_CITY_INTERVAL_SEC", 60),
|
||||
city_batch_size=_env_int("POLYWEATHER_WARMER_CITY_BATCH_SIZE", 8),
|
||||
|
||||
+11
-80
@@ -195,15 +195,11 @@ def _request_city_cache_refresh(
|
||||
kind: str,
|
||||
refresh_fn: Callable[[str, bool], Dict[str, Any]],
|
||||
) -> None:
|
||||
if _enqueue_collector_refresh_request(city, kind):
|
||||
return
|
||||
_start_city_cache_stale_refresh(city, kind, refresh_fn)
|
||||
_enqueue_collector_refresh_request(city, kind)
|
||||
|
||||
|
||||
def _request_city_full_refresh(city: str) -> None:
|
||||
if _enqueue_collector_refresh_request(city, "full"):
|
||||
return
|
||||
_start_city_full_stale_refresh(city)
|
||||
_enqueue_collector_refresh_request(city, "full")
|
||||
|
||||
|
||||
def _build_initializing_city_payload(city: str, *, detail_depth: str) -> Dict[str, Any]:
|
||||
@@ -298,44 +294,13 @@ async def _refresh_city_payload_with_stale_timeout(
|
||||
return canonical_payload
|
||||
return _queue_and_build_initializing_city_payload(city, kind=kind)
|
||||
|
||||
task, started = await _get_or_start_city_force_refresh_task(f"{kind}:{city}", refresh_factory)
|
||||
if not started:
|
||||
if cached_before_refresh:
|
||||
logger.warning(
|
||||
"city force refresh already running city={} kind={}; returning stale cache",
|
||||
city,
|
||||
kind,
|
||||
)
|
||||
return await _overlay_cached_wunderground(city, cached_before_refresh)
|
||||
canonical_payload = await _get_canonical_city_payload(city, detail_depth=kind)
|
||||
if canonical_payload:
|
||||
_enqueue_collector_refresh_request(city, kind, reason="force_refresh")
|
||||
logger.warning(
|
||||
"city force refresh returning canonical latest while refresh runs city={} kind={}",
|
||||
city,
|
||||
kind,
|
||||
)
|
||||
return canonical_payload
|
||||
timeout_sec = _city_force_refresh_timeout_sec()
|
||||
try:
|
||||
return await asyncio.wait_for(asyncio.shield(task), timeout=timeout_sec)
|
||||
except asyncio.TimeoutError:
|
||||
cached_payload = await _get_cached_city_payload(city, kind)
|
||||
if cached_payload:
|
||||
logger.warning(
|
||||
"city force refresh timed out city={} kind={} timeout_sec={}; returning stale cache",
|
||||
city,
|
||||
kind,
|
||||
timeout_sec,
|
||||
)
|
||||
return await _overlay_cached_wunderground(city, cached_payload)
|
||||
return await task
|
||||
except Exception:
|
||||
cached_payload = await _get_cached_city_payload(city, kind)
|
||||
if cached_payload:
|
||||
logger.warning("city force refresh failed city={} kind={}; returning stale cache", city, kind)
|
||||
return await _overlay_cached_wunderground(city, cached_payload)
|
||||
raise
|
||||
_enqueue_collector_refresh_request(city, kind, reason="force_refresh")
|
||||
logger.warning(
|
||||
"city force refresh queued collector refresh city={} kind={}; returning cached payload",
|
||||
city,
|
||||
kind,
|
||||
)
|
||||
return await _overlay_cached_wunderground(city, cached_before_refresh)
|
||||
|
||||
|
||||
async def _refresh_city_cache_with_stale_timeout(
|
||||
@@ -359,26 +324,7 @@ def _start_city_cache_stale_refresh(
|
||||
cache_kind = str(kind or "").strip().lower()
|
||||
if not normalized or not cache_kind:
|
||||
return
|
||||
key = f"{cache_kind}:{normalized}"
|
||||
existing = _CITY_STALE_REFRESH_TASKS.get(key)
|
||||
if existing is not None and not existing.done():
|
||||
return
|
||||
|
||||
async def _run_refresh() -> Dict[str, Any]:
|
||||
return await run_in_threadpool(refresh_fn, normalized, False)
|
||||
|
||||
task = asyncio.create_task(_run_refresh())
|
||||
_CITY_STALE_REFRESH_TASKS[key] = task
|
||||
|
||||
def _cleanup(done: "asyncio.Task[Dict[str, Any]]") -> None:
|
||||
if _CITY_STALE_REFRESH_TASKS.get(key) is done:
|
||||
_CITY_STALE_REFRESH_TASKS.pop(key, None)
|
||||
try:
|
||||
done.result()
|
||||
except Exception as exc: # pragma: no cover - defensive background guard
|
||||
logger.warning("city stale refresh failed city={} kind={}: {}", normalized, cache_kind, exc)
|
||||
|
||||
task.add_done_callback(_cleanup)
|
||||
_enqueue_collector_refresh_request(normalized, cache_kind, reason="stale_refresh")
|
||||
|
||||
|
||||
async def _overlay_cached_wunderground(city: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -500,22 +446,7 @@ def _start_city_full_stale_refresh(city: str) -> None:
|
||||
normalized = str(city or "").strip().lower()
|
||||
if not normalized:
|
||||
return
|
||||
existing = _CITY_FULL_STALE_REFRESH_TASKS.get(normalized)
|
||||
if existing is not None and not existing.done():
|
||||
return
|
||||
|
||||
task = asyncio.create_task(_refresh_city_full_data(city, False))
|
||||
_CITY_FULL_STALE_REFRESH_TASKS[normalized] = task
|
||||
|
||||
def _cleanup(done: "asyncio.Task[Dict[str, Any]]") -> None:
|
||||
if _CITY_FULL_STALE_REFRESH_TASKS.get(normalized) is done:
|
||||
_CITY_FULL_STALE_REFRESH_TASKS.pop(normalized, None)
|
||||
try:
|
||||
done.result()
|
||||
except Exception as exc: # pragma: no cover - defensive background guard
|
||||
logger.warning("city full stale refresh failed city={}: {}", city, exc)
|
||||
|
||||
task.add_done_callback(_cleanup)
|
||||
_enqueue_collector_refresh_request(normalized, "full", reason="stale_refresh")
|
||||
|
||||
|
||||
async def _get_city_full_data(city: str, *, force_refresh: bool) -> Dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user