"""Geo cache flush background task. Registers an APScheduler job that periodically persists newly resolved IP geo entries from the in-memory ``_dirty`` set to the ``geo_cache`` table. After Task 2 removed geo cache writes from GET requests, newly resolved IPs are only held in the in-memory cache until this task flushes them. With the default 60-second interval, at most one minute of new resolution results is at risk on an unexpected process restart. """ from __future__ import annotations from typing import TYPE_CHECKING, Any import structlog from app.db import open_db from app.utils.runtime_state import get_effective_settings if TYPE_CHECKING: import aiosqlite from app.config import Settings from app.services import geo_service if TYPE_CHECKING: from fastapi import FastAPI log: structlog.stdlib.BoundLogger = structlog.get_logger() #: How often the flush job fires (seconds). Configurable tuning constant. GEO_FLUSH_INTERVAL: int = 60 #: Stable APScheduler job ID — ensures re-registration replaces, not duplicates. JOB_ID: str = "geo_cache_flush" async def _get_db(settings: "Settings") -> tuple[aiosqlite.Connection, bool]: db = await open_db(settings.database_path) return db, True async def _run_flush_with_settings(settings: "Settings") -> None: """Flush the geo service dirty set to the application database. Args: settings: The resolved application settings used for database access. """ db, close_db = await _get_db(settings) try: count = await geo_service.flush_dirty(db) finally: if close_db: await db.close() if count > 0: log.debug("geo_cache_flush_ran", flushed=count) async def _run_flush(app: FastAPI) -> None: await _run_flush_with_settings(get_effective_settings(app)) def register(app: FastAPI) -> None: """Add (or replace) the geo cache flush job in the application scheduler. Must be called after the scheduler has been started (i.e., inside the lifespan handler, after ``scheduler.start()``). Args: app: The :class:`fastapi.FastAPI` application instance whose ``app.state.scheduler`` will receive the job. """ settings = get_effective_settings(app) app.state.scheduler.add_job( _run_flush_with_settings, trigger="interval", seconds=GEO_FLUSH_INTERVAL, kwargs={"settings": settings}, id=JOB_ID, replace_existing=True, ) log.info("geo_cache_flush_scheduled", interval_seconds=GEO_FLUSH_INTERVAL)