fix(worker): Worker 异常时将占位 Asset 标记为 ERROR #1651

Merged
auto-approve-bot merged 1 commits from fix/worker-asset-error-status into develop 2026-09-03 17:13:40 +08:00
Owner

问题

ingest_asset Worker 任务的 except 块原来只更新 job status 为 FAILED,没有处理上传时预先创建的 PROCESSING 状态 Asset。

如果 Worker 在处理过程中崩溃(ffmpeg 失败、OSS 超时等),Asset 会永远卡在 PROCESSING 状态,前端一直显示"处理中"。

修复

在 except 块中查找对应 storage_key 的 Asset,如果仍处于 PROCESSING 或 UPLOADING 状态,将其标记为 ERROR 并记录错误信息到 metadata。

关键设计

  • asset_repo 查找失败不影响 job status 更新(内层 try/except 隔离)
  • 只标记 PROCESSING/UPLOADING 状态的 Asset,避免覆盖已成功处理的 Asset
  • metadata 中记录 ingest_error 字段,便于前端/诊断页展示错误原因

关于 Bug A(TTS 保存不同步到 Assets 表)

经代码审查,save_tts_job_to_library() 已经在创建 Asset 记录:

  • mime_type=audio/mpeg(或其他音频格式)
  • duration=job.duration
  • status=AssetStatus.READY
  • 关联到用户 voice 素材库

GET /assets?kind=voicefile_type==audio 过滤,可以查到这些记录。Bug A 实际上已经在现有代码中实现了。

## 问题 `ingest_asset` Worker 任务的 except 块原来只更新 job status 为 FAILED,没有处理上传时预先创建的 PROCESSING 状态 Asset。 如果 Worker 在处理过程中崩溃(ffmpeg 失败、OSS 超时等),Asset 会永远卡在 PROCESSING 状态,前端一直显示"处理中"。 ## 修复 在 except 块中查找对应 storage_key 的 Asset,如果仍处于 PROCESSING 或 UPLOADING 状态,将其标记为 ERROR 并记录错误信息到 metadata。 ### 关键设计 - asset_repo 查找失败不影响 job status 更新(内层 try/except 隔离) - 只标记 PROCESSING/UPLOADING 状态的 Asset,避免覆盖已成功处理的 Asset - metadata 中记录 `ingest_error` 字段,便于前端/诊断页展示错误原因 ## 关于 Bug A(TTS 保存不同步到 Assets 表) 经代码审查,`save_tts_job_to_library()` 已经在创建 Asset 记录: - `mime_type=audio/mpeg`(或其他音频格式) - `duration=job.duration` - `status=AssetStatus.READY` - 关联到用户 voice 素材库 `GET /assets?kind=voice` 按 `file_type==audio` 过滤,可以查到这些记录。Bug A 实际上已经在现有代码中实现了。
xiaoxia added 1 commit 2026-09-03 17:05:29 +08:00
fix(worker): Worker 异常时将占位 Asset 标记为 ERROR,避免永远卡在 PROCESSING
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
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 / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped 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 / PR Build API Image (pull_request) Successful in 31s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 33s
AI Code Review / AI Code Review (pull_request) Failing after 1m18s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m36s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m54s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m2s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m16s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m53s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m59s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 7m11s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 9s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 5m10s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 12s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 33s
6426926844
ingest_asset 的 except 块原来只更新 job status 为 FAILED,没有处理
上传时预先创建的 PROCESSING 状态 Asset。如果 Worker 在处理过程中
崩溃(ffmpeg 失败、OSS 超时等),Asset 会永远卡在 PROCESSING。

修复:在 except 块中查找对应 storage_key 的 Asset,如果仍处于
PROCESSING 或 UPLOADING 状态,将其标记为 ERROR 并记录错误信息。
asset_repo 查找失败不影响 job status 更新(内层 try/except 隔离)。
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:是
  • 阻塞级问题数量:2 个

📊 审查概览

  • 整体评价:需修改
  • 建议级问题数量:1 个

🔴 阻塞级问题(必须修复)

  1. [apps/worker/worker_app/tasks/ingest.py: 735] job.storage_key 缺失空值检查导致 Job 状态更新失败

    • 问题类型:逻辑Bug / 数据损坏风险
    • 问题描述:代码直接使用 job.storage_key 调用 find_by_storage_key。如果 job.storage_keyNone(例如作业创建不完整或数据异常),将抛出异常。该异常会被外层的 except Exception 捕获(第 756 行),导致 db.rollback() 被调用,进而导致 job_repo.update(job) 的修改(标记为 FAILED)无法提交。结果是任务失败,但 Job 状态未更新,系统认为任务仍在运行。
    • 修改建议:在调用 find_by_storage_key 之前,必须判断 job.storage_key 是否存在。例如:
      if job.storage_key:
          existing = asset_repo.find_by_storage_key(job.storage_key)
          # ... 后续逻辑
      
  2. [apps/worker/worker_app/tasks/ingest.py: 743-746] 异常捕获后继续 Commit 存在 Session 状态损坏风险

    • 问题类型:逻辑Bug
    • 问题描述:在 SQLAlchemy 中,如果 asset_repo.update(existing) 内部发生数据库错误(如字段超长、约束冲突等),Session 会进入“错误状态”。代码在捕获异常后直接调用 db.commit(),极大概率再次抛出 InvalidRequestError(This Session is in an error state),导致 Job 的状态更新也无法提交。这违背了“尽力更新 Job 状态”的容错初衷。
    • 修改建议:Asset 的状态更新应该是“尽力而为”,不应影响 Job 状态的提交。建议使用嵌套事务(SAVEPOINT)或者独立的数据库会话来处理 Asset 的更新,或者确保 Asset 更新失败时能正确清理 Session 状态。最简单的修复方式是使用 db.begin_nested() 包裹 Asset 更新逻辑,或者在捕获异常后不继续执行当前的 commit,而是单独处理 Job 的提交。

💡 改进建议(不阻塞合并)

  1. [apps/worker/worker_app/tasks/ingest.py: 728, 730, 740] 重复获取当前时间
    • 具体内容:代码在 728、730、740 行三次调用 datetime.now(timezone.utc)。虽然差异极小,但在同一个逻辑上下文中,建议提取为变量 now = datetime.now(timezone.utc) 并复用,确保时间戳的一致性并提升代码整洁度。

良好实践

  • 在异常处理流程中尝试清理关联资源(Asset)的状态,避免数据处于中间态,设计思路正确。
  • 使用字典解包 {**(existing.metadata or {}), ...} 安全地更新 metadata,避免了 None 类型错误。

🤖 由 AI 代码审查机器人自动生成 | 2026-09-03 09:06:46 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:是 - 阻塞级问题数量:2 个 ### 📊 审查概览 - 整体评价:需修改 - 建议级问题数量:1 个 ### 🔴 阻塞级问题(必须修复) 1. **[apps/worker/worker_app/tasks/ingest.py: 735] `job.storage_key` 缺失空值检查导致 Job 状态更新失败** - 问题类型:逻辑Bug / 数据损坏风险 - 问题描述:代码直接使用 `job.storage_key` 调用 `find_by_storage_key`。如果 `job.storage_key` 为 `None`(例如作业创建不完整或数据异常),将抛出异常。该异常会被外层的 `except Exception` 捕获(第 756 行),导致 `db.rollback()` 被调用,进而导致 `job_repo.update(job)` 的修改(标记为 FAILED)无法提交。结果是任务失败,但 Job 状态未更新,系统认为任务仍在运行。 - 修改建议:在调用 `find_by_storage_key` 之前,必须判断 `job.storage_key` 是否存在。例如: ```python if job.storage_key: existing = asset_repo.find_by_storage_key(job.storage_key) # ... 后续逻辑 ``` 2. **[apps/worker/worker_app/tasks/ingest.py: 743-746] 异常捕获后继续 Commit 存在 Session 状态损坏风险** - 问题类型:逻辑Bug - 问题描述:在 SQLAlchemy 中,如果 `asset_repo.update(existing)` 内部发生数据库错误(如字段超长、约束冲突等),Session 会进入“错误状态”。代码在捕获异常后直接调用 `db.commit()`,极大概率再次抛出 `InvalidRequestError`(This Session is in an error state),导致 Job 的状态更新也无法提交。这违背了“尽力更新 Job 状态”的容错初衷。 - 修改建议:Asset 的状态更新应该是“尽力而为”,不应影响 Job 状态的提交。建议使用嵌套事务(SAVEPOINT)或者独立的数据库会话来处理 Asset 的更新,或者确保 Asset 更新失败时能正确清理 Session 状态。最简单的修复方式是使用 `db.begin_nested()` 包裹 Asset 更新逻辑,或者在捕获异常后不继续执行当前的 `commit`,而是单独处理 Job 的提交。 ### 💡 改进建议(不阻塞合并) 1. **[apps/worker/worker_app/tasks/ingest.py: 728, 730, 740] 重复获取当前时间** - 具体内容:代码在 728、730、740 行三次调用 `datetime.now(timezone.utc)`。虽然差异极小,但在同一个逻辑上下文中,建议提取为变量 `now = datetime.now(timezone.utc)` 并复用,确保时间戳的一致性并提升代码整洁度。 ### ✅ 良好实践 - 在异常处理流程中尝试清理关联资源(Asset)的状态,避免数据处于中间态,设计思路正确。 - 使用字典解包 `{**(existing.metadata or {}), ...}` 安全地更新 metadata,避免了 None 类型错误。 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-09-03 09:06:46 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->

🚀 预览环境已部署

项目 详情
PR号 #1651
预览链接 https://pr-1651.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1651 | | 预览链接 | [https://pr-1651.preview.xiaoxiajianji.com](https://pr-1651.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot merged commit e0e7f0a503 into develop 2026-09-03 17:13:40 +08:00
auto-approve-bot deleted branch fix/worker-asset-error-status 2026-09-03 17:13:41 +08:00

🗑️ 预览环境已清理

PR #1651 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1651 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.