"""Phase 0 worker stub: a heartbeat line every 30 s and a touched beat file; exits on SIGTERM.""" from __future__ import annotations import logging import os import signal import threading import time from pathlib import Path log = logging.getLogger("packetstation.worker") BEAT_SECONDS = 30 STALE_SECONDS = 90 def touch(path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.touch(exist_ok=True) now = time.time() os.utime(path, (now, now)) def run(beat_file: Path, build: str, stop: threading.Event | None = None, interval: float = BEAT_SECONDS) -> int: stop = stop or threading.Event() def _stop(signum: int, _frame: object) -> None: log.info("worker stopping on signal %s", signum) stop.set() if threading.current_thread() is threading.main_thread(): signal.signal(signal.SIGTERM, _stop) signal.signal(signal.SIGINT, _stop) log.info("worker started", extra={"ps": {"build": build, "beat_file": str(beat_file)}}) while not stop.is_set(): try: touch(beat_file) except OSError as e: log.error("can't touch beat file %s: %s", beat_file, e.strerror) log.info("heartbeat", extra={"ps": {"build": build}}) stop.wait(interval) log.info("worker stopped") return 0 def beat_age(beat_file: Path) -> float | None: try: return time.time() - beat_file.stat().st_mtime except OSError: return None