解决极速成片问题
This commit is contained in:
@@ -423,15 +423,23 @@ def _advance_script(job: QuickCreateJob) -> int | None:
|
||||
if not metadata.get("script_started"):
|
||||
from apps.projects.tasks import run_quick_script_task
|
||||
|
||||
from apps.common.celery_health import worker_consumes_queue
|
||||
|
||||
metadata["script_started"] = True
|
||||
metadata["script_started_at"] = timezone.now().isoformat()
|
||||
listens = worker_consumes_queue("airshelf.quick")
|
||||
if listens is False:
|
||||
metadata["script_local"] = True
|
||||
_save_job(
|
||||
job,
|
||||
metadata=metadata,
|
||||
progress=28,
|
||||
message="正在根据商品名称与图片生成带货脚本",
|
||||
)
|
||||
run_quick_script_task.apply_async(args=[str(job.id)], queue="airshelf.quick")
|
||||
if listens is False:
|
||||
_run_quick_script_in_thread(str(job.id))
|
||||
else:
|
||||
run_quick_script_task.apply_async(args=[str(job.id)], queue="airshelf.quick")
|
||||
return SCRIPT_POLL_SECONDS
|
||||
|
||||
latest = _latest_script_task(job.project)
|
||||
@@ -946,12 +954,26 @@ def _run_quick_script_in_thread(job_id: str) -> None:
|
||||
|
||||
|
||||
def _enqueue_advance(job: QuickCreateJob) -> None:
|
||||
from apps.common.celery_health import worker_consumes_queue
|
||||
from apps.projects.tasks import advance_quick_create_task
|
||||
|
||||
job_id = str(job.id)
|
||||
if worker_consumes_queue("airshelf.quick") is False:
|
||||
_advance_without_quick_queue(job_id)
|
||||
return
|
||||
try:
|
||||
advance_quick_create_task.apply_async(args=[job_id], queue="airshelf.quick")
|
||||
except Exception: # noqa: BLE001 — 队列不可用时就地推进一步
|
||||
_advance_without_quick_queue(job_id)
|
||||
|
||||
|
||||
def _advance_without_quick_queue(job_id: str) -> None:
|
||||
"""部署后的 worker 若只听 celery 队列,airshelf.quick 里的任务会永远没人拿。"""
|
||||
delay = advance_quick_create(job_id)
|
||||
job = QuickCreateJob.objects.filter(id=job_id).first()
|
||||
if job is None or delay is None:
|
||||
return
|
||||
if job.phase == QuickCreateJob.Phase.SCRIPT and not (job.metadata or {}).get("script_started"):
|
||||
advance_quick_create(job_id)
|
||||
|
||||
|
||||
@@ -1028,6 +1050,13 @@ def recover_quick_create(job: QuickCreateJob) -> None:
|
||||
return
|
||||
if _is_finished(job):
|
||||
return
|
||||
if job.status == QuickCreateJob.Status.QUEUED or job.phase == QuickCreateJob.Phase.PRODUCT:
|
||||
if timezone.now() - job.updated_at > STALE_AFTER:
|
||||
try:
|
||||
_advance_without_quick_queue(str(job.id))
|
||||
except Exception: # noqa: BLE001 — 恢复失败不能把进度接口打成 500
|
||||
logger.exception("quick create recover advance failed for job %s", job.id)
|
||||
return
|
||||
job_id = str(job.id)
|
||||
metadata = dict(job.metadata or {})
|
||||
if job.phase == QuickCreateJob.Phase.SCRIPT and metadata.get("script_started"):
|
||||
|
||||
@@ -48,7 +48,7 @@ class QuickCreateApiTests(TestCase):
|
||||
|
||||
@patch("apps.projects.services.quick_create.get_quick_script_model", return_value=object())
|
||||
@patch("apps.projects.views.get_default_model")
|
||||
@patch("apps.projects.views.advance_quick_create_task.apply_async")
|
||||
@patch("apps.projects.tasks.advance_quick_create_task.apply_async")
|
||||
@patch("apps.projects.views.require_worker_task")
|
||||
@patch("apps.projects.views._store_uploaded_asset")
|
||||
def test_submit_creates_product_project_and_persistent_job(self, store_asset, require_worker_task, enqueue, get_model, get_quick_model):
|
||||
@@ -106,7 +106,7 @@ class QuickCreateApiTests(TestCase):
|
||||
|
||||
@patch("apps.projects.services.quick_create.get_quick_script_model", return_value=object())
|
||||
@patch("apps.projects.views.get_default_model", return_value=object())
|
||||
@patch("apps.projects.views.advance_quick_create_task.apply_async")
|
||||
@patch("apps.projects.tasks.advance_quick_create_task.apply_async")
|
||||
@patch("apps.projects.views.require_worker_task")
|
||||
@patch("apps.projects.views._store_uploaded_asset")
|
||||
def test_submit_reuses_source_product_images(self, store_asset, require_worker_task, enqueue, get_model, get_quick_model):
|
||||
@@ -447,6 +447,32 @@ class QuickCreateCoordinatorTests(TestCase):
|
||||
self.assertEqual(self.job.status, QuickCreateJob.Status.RUNNING)
|
||||
run_local.assert_called_once_with(str(self.job.id))
|
||||
|
||||
def test_recover_starts_stale_queued_job_without_waiting_for_quick_queue(self):
|
||||
QuickCreateJob.objects.filter(id=self.job.id).update(
|
||||
status=QuickCreateJob.Status.QUEUED,
|
||||
phase=QuickCreateJob.Phase.PRODUCT,
|
||||
message="等待开始极速成片",
|
||||
updated_at=timezone.now() - timedelta(seconds=30),
|
||||
)
|
||||
self.job.refresh_from_db()
|
||||
with patch("apps.projects.services.quick_create._advance_without_quick_queue") as advance:
|
||||
recover_quick_create(self.job)
|
||||
advance.assert_called_once_with(str(self.job.id))
|
||||
|
||||
@patch("apps.common.celery_health.worker_consumes_queue", return_value=False)
|
||||
@patch("apps.projects.services.quick_create._run_quick_script_in_thread")
|
||||
def test_advance_script_runs_locally_when_quick_queue_has_no_consumer(self, run_local, _listens):
|
||||
self.job.status = QuickCreateJob.Status.RUNNING
|
||||
self.job.phase = QuickCreateJob.Phase.SCRIPT
|
||||
self.job.save(update_fields=["status", "phase", "updated_at"])
|
||||
|
||||
delay = advance_quick_create(str(self.job.id))
|
||||
self.job.refresh_from_db()
|
||||
self.assertEqual(delay, 5)
|
||||
self.assertTrue(self.job.metadata.get("script_started"))
|
||||
self.assertTrue(self.job.metadata.get("script_local"))
|
||||
run_local.assert_called_once_with(str(self.job.id))
|
||||
|
||||
@patch("apps.projects.services.quick_create._run_quick_script_in_thread")
|
||||
def test_recover_does_not_steal_script_when_model_call_is_inflight(self, run_local):
|
||||
self._script_task(AITask.Status.SUBMITTED, key="script-inflight-1")
|
||||
|
||||
@@ -90,7 +90,7 @@ from .services.pipeline import (
|
||||
)
|
||||
from .services.script_import import ScriptFileError, extract_script_text
|
||||
from .services.templates import build_template_fields, coerce_persona, coerce_template_combo, render_outline_text
|
||||
from .tasks import advance_quick_create_task, poll_video_segment_task
|
||||
from .tasks import poll_video_segment_task
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -661,7 +661,9 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
)
|
||||
|
||||
try:
|
||||
advance_quick_create_task.apply_async(args=[str(job.id)], queue="airshelf.quick")
|
||||
from .services.quick_create import _enqueue_advance
|
||||
|
||||
_enqueue_advance(job)
|
||||
except Exception as exc: # noqa: BLE001 — broker 极小窗口失败也必须给任务落终态
|
||||
from .services.quick_create import fail_quick_create
|
||||
|
||||
|
||||
Reference in New Issue
Block a user