fix(worker): P0 修复@celery_app.task装饰器错位导致所有生成任务崩溃
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 32s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 52s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 50s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m31s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m34s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m43s
AI Code Review / AI Code Review (pull_request) Failing after 1m50s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 32s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 52s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 50s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m31s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m34s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m43s
AI Code Review / AI Code Review (pull_request) Failing after 1m50s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
装饰器被误放在辅助函数 _sync_task_config_to_plan 上,导致 worker.generate_video 实际执行的是该辅助函数,Celery只传task_id 即抛 TypeError: missing db,staging 所有生成任务崩溃。 修复: 1. 将 @celery_app.task 移到真正的 generate_video 函数上方 2. _sync_task_config_to_plan 恢复为普通函数 3. 同步补回配音临时文件清理(squash合并时丢失) 4. 新增3个防回归单测,断言任务名/签名/辅助函数无装饰器 运行时验证: - generate_video.name == worker.generate_video - signature: (task_id: str) -> dict
This commit is contained in:
@@ -978,7 +978,7 @@ def _render_video(
|
||||
|
||||
Args:
|
||||
Returns:
|
||||
(output_path, render_duration, cover_candidates)
|
||||
(output_path, render_duration, cover_candidates, voiceover_path)
|
||||
"""
|
||||
if not downloaded_videos:
|
||||
raise RuntimeError(f"素材下载结果为空: task_id={task_id}")
|
||||
@@ -1206,13 +1206,6 @@ def _upload_and_record(
|
||||
# ── Celery Task ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@celery_app.task(
|
||||
bind=True,
|
||||
name="worker.generate_video",
|
||||
max_retries=2,
|
||||
soft_time_limit=600, # 10 分钟软超时
|
||||
time_limit=660, # 11 分钟硬超时
|
||||
)
|
||||
def _sync_task_config_to_plan(source_edit_plan_id: str, task_info: dict, db) -> str | None:
|
||||
"""将 GenerationTask 的配置同步到 EditPlan.config,返回配音本地路径(如果有)。
|
||||
|
||||
@@ -1295,7 +1288,7 @@ def _render_from_edit_plan(
|
||||
"""从 EditPlan 数据库记录直接渲染(不再内存重建clips)。
|
||||
|
||||
Returns:
|
||||
(output_path, render_duration, cover_candidates)
|
||||
(output_path, render_duration, cover_candidates, voiceover_path)
|
||||
"""
|
||||
from video_processing.render_adapter import RenderAdapter
|
||||
from worker_app.db import SessionLocal
|
||||
@@ -1335,13 +1328,18 @@ def _render_from_edit_plan(
|
||||
output_path = result.output_path
|
||||
cover_candidates = getattr(result, "cover_candidates", None)
|
||||
|
||||
return output_path, result.duration, cover_candidates
|
||||
return output_path, result.duration, cover_candidates, voiceover_path
|
||||
finally:
|
||||
db.close()
|
||||
# 清理临时配音文件
|
||||
# voiceover_path 在外部作用域,这里不直接引用
|
||||
|
||||
|
||||
@celery_app.task(
|
||||
bind=True,
|
||||
name="worker.generate_video",
|
||||
max_retries=2,
|
||||
soft_time_limit=600, # 10 分钟软超时
|
||||
time_limit=660, # 11 分钟硬超时
|
||||
)
|
||||
def generate_video(self, task_id: str) -> dict:
|
||||
"""生成视频任务 — 使用 UnifiedRenderService 统一渲染。
|
||||
|
||||
@@ -1439,7 +1437,7 @@ def generate_video(self, task_id: str) -> dict:
|
||||
gen_task.append_log("渲染模式", "从草稿数据渲染(与预览一致)")
|
||||
_flush_logs(task_id, gen_task)
|
||||
|
||||
output_path, render_duration, cover_candidates = _render_from_edit_plan(
|
||||
output_path, render_duration, cover_candidates, voiceover_tmp_path = _render_from_edit_plan(
|
||||
task_id=task_id,
|
||||
source_edit_plan_id=source_edit_plan_id,
|
||||
task_info=task_info,
|
||||
@@ -1587,6 +1585,13 @@ def generate_video(self, task_id: str) -> dict:
|
||||
file_size,
|
||||
)
|
||||
|
||||
# 清理临时配音文件
|
||||
if voiceover_tmp_path:
|
||||
try:
|
||||
Path(voiceover_tmp_path).unlink(missing_ok=True)
|
||||
except OSError:
|
||||
logger.warning("[task_id=%s] 清理临时配音文件失败: %s", task_id, voiceover_tmp_path)
|
||||
|
||||
return {
|
||||
"status": "completed",
|
||||
"task_id": task_id,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Regression test: ensure worker.generate_video Celery task is bound to the
|
||||
real generate_video function, not a helper introduced above it.
|
||||
|
||||
Context (P0 incident 2026-08-23): a refactor inserted helper function
|
||||
_sync_task_config_to_plan directly under the @celery_app.task decorator,
|
||||
so Celery registered the helper as "worker.generate_video". Calling the
|
||||
task with a single task_id raised TypeError and every generation job
|
||||
failed immediately. This test pins the decorator target.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
|
||||
|
||||
def test_generate_video_task_registered_under_expected_name():
|
||||
from worker_app.tasks.generation import generate_video
|
||||
|
||||
# Celery task object exposes its registered name
|
||||
assert generate_video.name == "worker.generate_video"
|
||||
|
||||
|
||||
def test_generate_video_task_signature_has_task_id():
|
||||
from worker_app.tasks.generation import generate_video
|
||||
|
||||
# The underlying callable must accept (self, task_id) for bind=True tasks
|
||||
sig = inspect.signature(generate_video.run)
|
||||
assert "task_id" in sig.parameters
|
||||
# The first positional arg after self must be task_id
|
||||
params = list(sig.parameters)
|
||||
assert params[0] == "task_id"
|
||||
|
||||
|
||||
def test_sync_task_config_to_plan_is_plain_function():
|
||||
"""Helper must NOT be registered as a Celery task."""
|
||||
from worker_app.tasks.generation import _sync_task_config_to_plan
|
||||
|
||||
assert not hasattr(_sync_task_config_to_plan, "run"), (
|
||||
"_sync_task_config_to_plan must be a plain function, not a Celery task"
|
||||
)
|
||||
Reference in New Issue
Block a user