Files
instameex/app/jobs.py
T
2026-07-30 22:31:34 +02:00

66 lines
1.7 KiB
Python

"""In-memory job registry and lifecycle (creation, heartbeat, reaping).
Single-process only: JOBS lives in this module's memory, so this assumes
one gunicorn worker (see Dockerfile). Multiple workers would each keep a
separate registry and requests would 404 depending on which one handled
them.
"""
import shutil
import threading
import time
from pathlib import Path
from .config import HEARTBEAT_TIMEOUT, JOBS_DIR, REAPER_INTERVAL
JOBS: dict[str, dict] = {}
def reset_jobs_dir() -> None:
"""Wipe any leftovers from a previous run (crash, forced restart) —
the in-memory registry never survives a restart anyway, so orphaned
job dirs on disk would otherwise sit there forever."""
shutil.rmtree(JOBS_DIR, ignore_errors=True)
JOBS_DIR.mkdir(parents=True, exist_ok=True)
def new_job_dir(job_id: str) -> Path:
job_dir = JOBS_DIR / job_id
job_dir.mkdir(parents=True, exist_ok=True)
return job_dir
def register(job_id: str, **fields) -> None:
JOBS[job_id] = {**fields, "last_seen": time.time()}
def get(job_id: str) -> dict | None:
return JOBS.get(job_id)
def touch(job_id: str) -> None:
job = JOBS.get(job_id)
if job:
job["last_seen"] = time.time()
def discard_dir(job_dir: Path) -> None:
shutil.rmtree(job_dir, ignore_errors=True)
def cleanup(job_id: str) -> None:
JOBS.pop(job_id, None)
discard_dir(JOBS_DIR / job_id)
def _reap_stale_jobs() -> None:
while True:
time.sleep(REAPER_INTERVAL)
cutoff = time.time() - HEARTBEAT_TIMEOUT
stale = [jid for jid, job in list(JOBS.items()) if job.get("last_seen", 0) < cutoff]
for jid in stale:
cleanup(jid)
def start_reaper() -> None:
threading.Thread(target=_reap_stale_jobs, daemon=True).start()