"""Entrypoint for the scheduler process. This module starts the APScheduler scheduler and ensures a graceful shutdown on SIGINT/SIGTERM. It also improves job error logging to include exception tracebacks for easier debugging. """ import datetime import signal import sys import traceback from functools import partial from typing import Any from apscheduler.events import EVENT_JOB_ERROR from apscheduler.schedulers.blocking import BlockingScheduler from config import config from log.log_manager import log, logger from task.manager_task import manager_task def _format_exception(exc: BaseException) -> str: """Return a nicely formatted exception with traceback.""" return "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)) def job_error_listener(event: Any) -> None: """Listener for job errors that logs the exception and traceback. Uses the APScheduler event object. This is defensive: if an exception is present it logs its string representation and the full traceback to the configured logger. Keep this lightweight to avoid raising inside the listener. """ try: if getattr(event, "exception", None): exc = event.exception logger.error(f"Job {getattr(event, 'job_id', '')} crashed: {exc}") logger.error(_format_exception(exc)) # 可添加邮件告警逻辑(如果需要且已配置) except Exception: # We must not let the listener raise — log and continue. logger.exception("Exception in job_error_listener") def _validate_interval(value: Any) -> int: """Return a valid positive integer interval in seconds. If the provided value is invalid, returns a safe default (300). """ DEFAULT = 300 try: iv = int(value) if iv <= 0: raise ValueError("interval must be > 0") return iv except Exception: logger.warning( "Invalid config.scheduler_interval=%r, falling back to %s seconds", value, DEFAULT, ) return DEFAULT def main() -> None: """Create and start the scheduler with graceful shutdown handling.""" scheduler = BlockingScheduler() interval_seconds = _validate_interval(getattr(config, "scheduler_interval", None)) # 每隔 interval_seconds 秒执行一次任务,同时设定第一次执行在程序启动10秒后执行 scheduler.add_job( partial(manager_task, scheduler), "interval", seconds=interval_seconds, jitter=30, next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=10), ) # 添加任务错误监听器 scheduler.add_listener(job_error_listener, EVENT_JOB_ERROR) # Graceful shutdown handlers def _shutdown(signum, frame): log(f"Received signal {signum}. Shutting down scheduler...") try: scheduler.shutdown(wait=False) except Exception: logger.exception("Error while shutting down scheduler") # Ensure process exits after scheduler shutdown # sys.exit(0) signal.signal(signal.SIGINT, _shutdown) signal.signal(signal.SIGTERM, _shutdown) try: log("started successfully.") scheduler.start() # 阻塞运行 except (KeyboardInterrupt, SystemExit): log("Shutting down ...") if scheduler.state == 1: try: scheduler.shutdown(wait=False) except Exception: logger.exception("Error while shutting down scheduler") if __name__ == "__main__": main()