server-up/app/core/jobs.py
2026-05-09 13:55:29 +00:00

74 lines
1.9 KiB
Python

"""Background job runner with log streaming."""
import threading, time, uuid, queue
_jobs: dict[str, dict] = {}
_queues: dict[str, queue.Queue] = {}
_stream_locks: dict[str, threading.Lock] = {}
_lock = threading.Lock()
_TTL = 3600
def _cleanup():
"""Verwijder voltooide jobs ouder dan TTL uit geheugen."""
cutoff = time.time() - _TTL
with _lock:
stale = [jid for jid, j in _jobs.items()
if j["status"] != "running" and j["ts"] < cutoff]
for jid in stale:
_jobs.pop(jid, None)
_queues.pop(jid, None)
_stream_locks.pop(jid, None)
def create(tag: str) -> tuple[str, queue.Queue]:
_cleanup()
jid = uuid.uuid4().hex[:8]
q = queue.Queue()
with _lock:
_jobs[jid] = {"id": jid, "tag": tag, "status": "running",
"lines": [], "ts": time.time()}
_queues[jid] = q
_stream_locks[jid] = threading.Lock()
return jid, q
def get_queue(jid: str) -> queue.Queue | None:
return _queues.get(jid)
def log(q, level: str, text: str):
if q:
q.put({"level": level, "text": text})
def finish(jid: str, status="done"):
with _lock:
if jid in _jobs:
_jobs[jid]["status"] = status
def done(q):
if q:
q.put(None)
def stream(jid: str, offset=0) -> dict:
job = _jobs.get(jid)
if not job:
return {"lines": [], "status": "unknown"}
with _stream_locks.get(jid, threading.Lock()):
q = _queues.get(jid)
if q:
while True:
try:
item = q.get_nowait()
if item is None:
break
job["lines"].append(item)
except queue.Empty:
break
return {"lines": job["lines"][offset:], "status": job["status"]}
def run(fn, *args):
threading.Thread(target=fn, args=args, daemon=True).start()