import logging import threading import time from datetime import datetime, timezone from ..database import SessionLocal from ..models import ParseJob from .jobs import enqueue_job logger = logging.getLogger(__name__) TICK_SECONDS = 30 RECURRING_STATUSES = ("completed", "failed") def run_scheduler_tick() -> None: db = SessionLocal() try: now = datetime.now(timezone.utc) jobs = ( db.query(ParseJob) .filter( ParseJob.is_active.is_(True), ParseJob.interval_seconds > 0, ParseJob.status.in_(RECURRING_STATUSES), ) .all() ) for job in jobs: if job.last_run_at is None: continue elapsed = (now - job.last_run_at).total_seconds() if elapsed < job.interval_seconds: continue job.status = "queued" job.last_error = None db.commit() enqueue_job(job.id, job.source_type, job.source_config) logger.info("Re-queued recurring job %s (interval %ss)", job.id, job.interval_seconds) except Exception: logger.exception("Scheduler tick failed") db.rollback() finally: db.close() def _scheduler_loop(stop_event: threading.Event) -> None: while not stop_event.wait(TICK_SECONDS): run_scheduler_tick() def start_scheduler() -> threading.Event: stop_event = threading.Event() thread = threading.Thread(target=_scheduler_loop, args=(stop_event,), daemon=True) thread.start() logger.info("Parse job scheduler started (tick every %ss)", TICK_SECONDS) return stop_event