"""Liveness: a background task touches a file while the event loop runs; the Docker healthcheck checks its age. A stuck loop stops touching the file, so the container turns unhealthy even though the process is still up. `python -m app.health` exits 0 when healthy, 1 otherwise. """ import asyncio import sys import time from pathlib import Path from app.config import RuntimeSettings async def heartbeat(path: Path, interval: float) -> None: while True: await asyncio.to_thread(path.touch) await asyncio.sleep(interval) def is_alive(path: Path, max_age: float, now: float | None = None) -> bool: try: age = (time.time() if now is None else now) - path.stat().st_mtime except FileNotFoundError: return False return age <= max_age def main() -> None: rt = RuntimeSettings() sys.exit(0 if is_alive(rt.heartbeat_file, rt.heartbeat_max_age) else 1) if __name__ == "__main__": main()