Files
cmhub/apps/api/management/commands/run_image_tasks.py
T

99 lines
3.2 KiB
Python
Raw Normal View History

2026-07-08 22:08:48 +08:00
import time
import uuid
from django.conf import settings
from django.core.management.base import BaseCommand
2026-07-09 14:46:09 +08:00
from apps.api.image_tasks import (
image_task_max_attempts,
reap_stale_image_tasks,
run_one_image_task,
)
2026-07-09 11:57:06 +08:00
from apps.api.models import ImageGenerationTask
2026-07-08 22:08:48 +08:00
class Command(BaseCommand):
help = "Run asynchronous image generation tasks."
def add_arguments(self, parser):
parser.add_argument(
"--once",
action="store_true",
help="Process at most one queued task and exit.",
)
parser.add_argument(
"--worker-id",
default="",
help="Stable worker identifier recorded on claimed tasks.",
)
parser.add_argument(
"--sleep-seconds",
type=float,
default=1.0,
help="Sleep interval when no queued task is available.",
)
parser.add_argument(
"--reap-only",
action="store_true",
help="Only reap stale running tasks and exit.",
)
def handle(self, *args, **options):
worker_id = options["worker_id"] or f"image-worker-{uuid.uuid4().hex[:8]}"
once = bool(options["once"])
reap_only = bool(options["reap_only"])
sleep_seconds = max(0.1, float(options["sleep_seconds"]))
reaper_interval = max(
1,
int(getattr(settings, "IMAGE_TASK_REAPER_INTERVAL_SECONDS", 60)),
)
last_reap_at = 0.0
while True:
now = time.monotonic()
if reap_only or now - last_reap_at >= reaper_interval:
reaped = reap_stale_image_tasks()
if reaped:
self.stdout.write(f"reaped={reaped}")
last_reap_at = now
if reap_only:
return
task = run_one_image_task(worker_id=worker_id)
if task is not None:
2026-07-09 11:57:06 +08:00
self.stdout.write(format_task_log_line(task, started_at=now))
2026-07-08 22:08:48 +08:00
if once:
return
continue
if once:
return
time.sleep(sleep_seconds)
2026-07-09 11:57:06 +08:00
def format_task_log_line(task: ImageGenerationTask, *, started_at: float) -> str:
duration_ms = max(0, int((time.monotonic() - started_at) * 1000))
alias = str((task.request_payload or {}).get("model") or "")
2026-07-09 14:46:09 +08:00
retrying = bool(
task.status == ImageGenerationTask.Status.QUEUED
and task.error_code
and task.next_attempt_at
)
2026-07-09 11:57:06 +08:00
fields = {
"event": "image_task_processed",
"task_id": str(task.task_id),
"status": task.status,
"alias": alias,
2026-07-09 14:46:09 +08:00
"attempt": str(task.attempt_count),
"max_attempts": str(image_task_max_attempts()),
"retrying": str(retrying).lower(),
"next_attempt_at": task.next_attempt_at.isoformat() if task.next_attempt_at else "",
2026-07-09 11:57:06 +08:00
"duration_ms": str(duration_ms),
}
2026-07-09 14:46:09 +08:00
if retrying or task.status in {
ImageGenerationTask.Status.FAILED,
ImageGenerationTask.Status.EXPIRED,
}:
2026-07-09 11:57:06 +08:00
fields["error_code"] = task.error_code or "upstream_error"
return " ".join(f"{key}={value}" for key, value in fields.items())