74 lines
1.9 KiB
Python
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()
|