Files
bakery-ia/services/external/app/services/poi_scheduler.py

188 lines
5.3 KiB
Python

"""
POI Refresh Scheduler
Background scheduler for periodic POI context refresh.
Runs every hour to check for and execute pending POI refresh jobs.
"""
import asyncio
from typing import Optional
from datetime import datetime, timezone
import structlog
from app.services.poi_refresh_service import POIRefreshService
logger = structlog.get_logger()
class POIRefreshScheduler:
"""
POI Refresh Scheduler
Background task that periodically checks for and executes
pending POI refresh jobs.
"""
def __init__(
self,
poi_refresh_service: Optional[POIRefreshService] = None,
check_interval_seconds: int = 3600, # 1 hour
max_concurrent_jobs: int = 5
):
"""
Initialize POI refresh scheduler.
Args:
poi_refresh_service: POI refresh service instance
check_interval_seconds: Seconds between checks (default: 3600 = 1 hour)
max_concurrent_jobs: Max concurrent job executions (default: 5)
"""
self.poi_refresh_service = poi_refresh_service or POIRefreshService()
self.check_interval_seconds = check_interval_seconds
self.max_concurrent_jobs = max_concurrent_jobs
self._task: Optional[asyncio.Task] = None
self._running = False
logger.info(
"POI Refresh Scheduler initialized",
check_interval_seconds=check_interval_seconds,
max_concurrent_jobs=max_concurrent_jobs
)
async def start(self):
"""Start the scheduler background task"""
if self._running:
logger.warning("POI Refresh Scheduler already running")
return
self._running = True
self._task = asyncio.create_task(self._run_scheduler())
logger.info("POI Refresh Scheduler started")
async def stop(self):
"""Stop the scheduler background task"""
if not self._running:
return
self._running = False
if self._task:
self._task.cancel()
try:
await self._task
except asyncio.CancelledError:
pass
logger.info("POI Refresh Scheduler stopped")
async def _run_scheduler(self):
"""Main scheduler loop"""
logger.info("POI Refresh Scheduler loop started")
while self._running:
try:
await self._process_cycle()
except Exception as e:
logger.error(
"POI refresh scheduler cycle failed",
error=str(e),
exc_info=True
)
# Wait for next cycle
try:
await asyncio.sleep(self.check_interval_seconds)
except asyncio.CancelledError:
break
logger.info("POI Refresh Scheduler loop ended")
async def _process_cycle(self):
"""Process one scheduler cycle"""
cycle_start = datetime.now(timezone.utc)
logger.debug(
"POI refresh scheduler cycle started",
timestamp=cycle_start.isoformat()
)
# Process pending jobs
result = await self.poi_refresh_service.process_pending_jobs(
max_concurrent=self.max_concurrent_jobs
)
cycle_end = datetime.now(timezone.utc)
cycle_duration = (cycle_end - cycle_start).total_seconds()
if result["total_jobs"] > 0:
logger.info(
"POI refresh scheduler cycle completed",
total_jobs=result["total_jobs"],
successful=result["successful"],
failed=result["failed"],
cycle_duration_seconds=cycle_duration
)
else:
logger.debug(
"POI refresh scheduler cycle completed (no jobs)",
cycle_duration_seconds=cycle_duration
)
async def trigger_immediate_check(self):
"""Trigger an immediate check for pending jobs (bypasses schedule)"""
logger.info("POI refresh scheduler immediate check triggered")
try:
result = await self.poi_refresh_service.process_pending_jobs(
max_concurrent=self.max_concurrent_jobs
)
logger.info(
"POI refresh scheduler immediate check completed",
total_jobs=result["total_jobs"],
successful=result["successful"],
failed=result["failed"]
)
return result
except Exception as e:
logger.error(
"POI refresh scheduler immediate check failed",
error=str(e),
exc_info=True
)
raise
@property
def is_running(self) -> bool:
"""Check if scheduler is running"""
return self._running
# Global scheduler instance
_scheduler_instance: Optional[POIRefreshScheduler] = None
def get_scheduler() -> POIRefreshScheduler:
"""Get global scheduler instance (singleton)"""
global _scheduler_instance
if _scheduler_instance is None:
_scheduler_instance = POIRefreshScheduler()
return _scheduler_instance
async def start_scheduler():
"""Start global POI refresh scheduler"""
scheduler = get_scheduler()
await scheduler.start()
async def stop_scheduler():
"""Stop global POI refresh scheduler"""
scheduler = get_scheduler()
await scheduler.stop()