Files
arlo/arlo.py
konjacpotato 52d5bbc216 优化
2025-11-14 21:45:00 +08:00

112 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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', '<unknown>')} 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()