feat: retry async image tasks
This commit is contained in:
+35
@@ -1805,3 +1805,38 @@
|
||||
- 新增 `next_attempt_at` 与 `IMAGE_TASK_MAX_RETRIES` / `IMAGE_TASK_RETRY_BACKOFF_SECONDS` 等配置;worker 只抢到达重试时间的 queued 任务。
|
||||
- 轮询响应可兼容性新增 `attempt_count`、`max_attempts`、`next_attempt_at`;日志新增 attempt / retrying / next_attempt_at 字段。
|
||||
- 验证:仅文档更新;提交前执行 `git diff --check`。
|
||||
|
||||
## 2026-07-09 实施:T-616 生图失败自动重试 2 次
|
||||
|
||||
- 状态:DONE。
|
||||
- 背景:线上异步生图已出现 `api.vectorengine.ai` 偶发 `Read timed out`。原逻辑第一次 `upstream_timeout` 就把任务置 `failed` 并退点,批量生图成功率受上游短暂波动影响较大。
|
||||
- 代码变更:
|
||||
- `apps/api/models.py` / `apps/api/migrations/0002_imagegenerationtask_next_attempt_at_and_more.py`:给 `ImageGenerationTask` 增加 `next_attempt_at`,并新增 `(status, next_attempt_at)` 索引,worker 只抢到达重试时间的 queued 任务。
|
||||
- `config/settings.py` / `.env.example`:新增 `IMAGE_TASK_MAX_RETRIES=2`、`IMAGE_TASK_RETRY_BACKOFF_SECONDS=10,30`。
|
||||
- `apps/api/generation.py`:`execute_precharged_generation()` 增加 `refund_on_failure` 参数;旧同步接口默认行为不变,异步 worker 可在上游失败时先不退款,由任务状态机决定重试或最终退款。
|
||||
- `apps/api/image_tasks.py`:只对 `upstream_timeout` / `upstream_error` 自动重试;前两次临时性失败回到 `queued`、写 `next_attempt_at`、保留 `CallRecord.pending`、不退点;不可重试错误或最终失败才幂等退款。轮询响应新增 `attempt_count`、`max_attempts`、`next_attempt_at`。
|
||||
- `apps/api/management/commands/run_image_tasks.py`:worker 日志增加 `attempt`、`max_attempts`、`retrying`、`next_attempt_at`,重试中 / 失败 / 过期任务输出 `error_code`。
|
||||
- `apps/api/admin.py`:后台图片任务列表 / 过滤 / 只读字段展示 `next_attempt_at`。
|
||||
- `apps/api/tests.py`:新增 T-616 目标测试,覆盖首次超时回队列不退点、两次超时后三次成功只扣一次、连续三次超时最终只退一次、非重试错误立即退款、worker 重试日志字段不泄露敏感信息;旧失败退款测试用 `IMAGE_TASK_MAX_RETRIES=0` 保留原验收。
|
||||
- 文档变更:
|
||||
- `docs/api.md`:异步提交 / 轮询响应示例新增 `attempt_count`、`max_attempts`、`next_attempt_at`,说明 `queued + next_attempt_at` 为等待自动重试。
|
||||
- `docs/04-architecture.md`:同步 `next_attempt_at` schema、索引、异步计费状态机和临时性失败重试语义。
|
||||
- `docs/env.md` / `docs/deployment.md`:同步新增环境变量、最坏耗时估算和 worker 日志排障口径。
|
||||
- `docs/current-state.md`:同步 T-616 已完成、最新验证和下一步。
|
||||
- `docs/06-tasks.md`:T-616 标记为 DONE。
|
||||
- 验证:
|
||||
- `py -3.12 -m py_compile apps\api\generation.py apps\api\image_tasks.py apps\api\management\commands\run_image_tasks.py apps\api\models.py apps\api\admin.py apps\api\tests.py config\settings.py`:通过。
|
||||
- `py -3.12 manage.py makemigrations api`:生成 `api.0002_imagegenerationtask_next_attempt_at_and_more`。
|
||||
- `py -3.12 manage.py migrate api --noinput`:通过,已应用 `api.0001_initial` 与 `api.0002_imagegenerationtask_next_attempt_at_and_more` 到当前开发库。
|
||||
- `py -3.12 manage.py check`:通过,0 issues。
|
||||
- `py -3.12 manage.py makemigrations --check --dry-run`:通过,No changes detected。
|
||||
- `py -3.12 manage.py test apps.api.tests.GenerateApiTests.test_async_image_retryable_timeout_requeues_without_refund_and_respects_backoff apps.api.tests.GenerateApiTests.test_async_image_retryable_timeouts_then_success_charges_once apps.api.tests.GenerateApiTests.test_async_image_retryable_timeouts_final_failure_refunds_once apps.api.tests.GenerateApiTests.test_async_image_non_retryable_provider_error_fails_immediately_and_refunds apps.api.tests.GenerateApiTests.test_run_image_tasks_logs_retrying_task_attempt_fields_without_sensitive_data --keepdb --noinput --verbosity 2`:通过,5 tests OK。
|
||||
- `py -3.12 manage.py test apps.api.tests.GenerateApiTests.test_async_image_submit_usage_telemetry_logs_safe_success_event apps.api.tests.GenerateApiTests.test_async_image_submit_usage_telemetry_logs_error_code_without_sensitive_data apps.api.tests.GenerateApiTests.test_async_image_blocked_prompt_creates_no_task_or_charge apps.api.tests.GenerateApiTests.test_async_image_insufficient_points_returns_402_without_task apps.api.tests.GenerateApiTests.test_async_image_idempotency_reuses_task_and_rejects_conflict apps.api.tests.GenerateApiTests.test_async_image_worker_success_and_poll_are_idempotent apps.api.tests.GenerateApiTests.test_async_image_poll_rejects_cross_user_access apps.api.tests.GenerateApiTests.test_async_image_worker_failure_refunds_precharged_points apps.api.tests.GenerateApiTests.test_run_image_tasks_logs_failed_task_alias_error_and_duration apps.api.tests.GenerateApiTests.test_async_image_reaper_fails_stale_running_task_and_refunds apps.api.tests.GenerateApiTests.test_async_image_duplicate_worker_does_not_double_charge_or_refund apps.api.tests.GenerateApiTests.test_async_image_late_worker_after_reaper_cannot_flip_failed_task --keepdb --noinput --verbosity 2`:通过,12 tests OK。
|
||||
- `py -3.12 manage.py test apps.api.tests.GenerateApiTests --keepdb --noinput --verbosity 2`:通过,37 tests OK。
|
||||
- `.\init.ps1`:通过(Python 3.12.3,依赖已满足,`manage.py check` 0 issues,打印启动命令)。
|
||||
- `git diff --check`:通过,仅 Windows CRLF 提示。
|
||||
- 测试环境现象:
|
||||
- 测试期仍保留 allauth `account.EmailAddress` 条件唯一约束在 MySQL 上不可创建的既有 `models.W036` 警告。
|
||||
- 决策:
|
||||
- T-616 第一版不重试敏感词、余额不足、未定价、模型 / 别名不可用、参数错误和 Provider 能力错误;这些属于确定性失败,立即失败并退款或在提交阶段不扣点。
|
||||
- 重试期间不展示 `error` 给轮询客户端,客户端只需看到 `queued + next_attempt_at` 后继续轮询;最终 `failed` 才表示已退款。
|
||||
|
||||
Reference in New Issue
Block a user