Files
HSAP/platform/as_platform/jobs/queue.py
Chengfang Lu 7c43b44c57 feat: initial HSAP platform
Huaxu Sentinel Active Safety Platform with embedded algorithm code,
Docker Compose setup, and vendored dataset scaffolds for clone-and-run.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 16:59:59 +08:00

146 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Job 队列PostgreSQL + 可选 Redis Worker"""
from __future__ import annotations
import threading
import uuid
from datetime import datetime, timezone
from typing import Any
from as_platform.config import JOB_EXECUTOR
from as_platform.db.engine import session_scope
from as_platform.db.models import Job
_executor_lock = threading.Lock()
def _now() -> datetime:
return datetime.now(timezone.utc)
def _new_id() -> str:
return f"job-{datetime.now().strftime('%Y%m%d')}-{uuid.uuid4().hex[:8]}"
def enqueue_job(
action: str,
params: dict[str, Any],
*,
approval_id: str | None = None,
async_run: bool = True,
) -> dict[str, Any]:
job_id = _new_id()
with session_scope() as db:
job = Job(
id=job_id,
status="queued",
action=action,
approval_id=approval_id,
created_at=_now(),
)
job.set_params(params)
db.add(job)
out = get_job(job_id) or {"id": job_id, "status": "queued", "action": action}
if not async_run:
_run_job(job_id)
return get_job(job_id) or out
if JOB_EXECUTOR == "worker":
from as_platform.redis.bus import push_job
push_job(job_id)
return out
threading.Thread(target=_run_job, args=(job_id,), daemon=True).start()
return out
def get_job(job_id: str) -> dict[str, Any] | None:
with session_scope() as db:
rec = db.get(Job, job_id)
return rec.to_dict() if rec else None
def list_jobs(status: str | None = None, limit: int = 100) -> list[dict[str, Any]]:
with session_scope() as db:
q = db.query(Job).order_by(Job.created_at.desc())
if status:
q = q.filter(Job.status == status)
return [j.to_dict() for j in q.limit(limit).all()]
def _patch(job_id: str, **fields: Any) -> dict[str, Any] | None:
with session_scope() as db:
rec = db.get(Job, job_id)
if not rec:
return None
for k, v in fields.items():
if k == "result" and isinstance(v, dict):
rec.set_result(v)
elif hasattr(rec, k):
setattr(rec, k, v)
db.flush()
return rec.to_dict()
def _compact_result(payload: Any) -> dict[str, Any]:
if isinstance(payload, dict):
out = dict(payload)
else:
out = {"value": payload}
if "ok" not in out:
out["ok"] = True
for k in ("stdout", "stderr"):
if isinstance(out.get(k), str):
out[k] = out[k][-8000:]
return out
def _run_job(job_id: str) -> None:
with _executor_lock:
job = get_job(job_id)
if not job or job.get("status") not in ("queued",):
return
_patch(job_id, status="running", started_at=_now())
from as_platform.agents.trace import trace_span
from as_platform.jobs.runner import execute_action
from as_platform.redis.bus import publish
publish("job.started", {"job_id": job_id, "action": job["action"]})
try:
with trace_span("job_start", job_id=job_id, action=job["action"], approval_id=job.get("approval_id")):
result = execute_action(job["action"], job.get("params") or {})
persisted = _compact_result(result)
_patch(
job_id,
status="succeeded",
finished_at=_now(),
result=persisted,
)
publish("job.succeeded", {"job_id": job_id})
with trace_span("job_end", job_id=job_id, status="succeeded"):
pass
_sync_approval(job.get("approval_id"), "executed", persisted)
except Exception as e:
_patch(job_id, status="failed", finished_at=_now(), result={"ok": False, "error": str(e)})
publish("job.failed", {"job_id": job_id, "error": str(e)})
with trace_span("job_end", job_id=job_id, status="failed", error=str(e)):
pass
_sync_approval(job.get("approval_id"), "failed", {"error": str(e)})
def _sync_approval(approval_id: str | None, status: str, result: dict) -> None:
if not approval_id:
return
from as_platform.audit.queue import _update, _now as audit_now
_update(
approval_id,
status=status,
executed_at=audit_now(),
result=result if isinstance(result, dict) and "ok" in result else {"ok": status == "executed", **result},
)