# 变更审计记录：僵尸任务修复 + 优雅关闭 + 重新执行按钮

| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-22 |
| 触发原因 | 执行 ID bb9a0761 永远卡在 running 状态，uvicorn 重启导致子进程丢失 |

## 操作摘要

解决后端重启时 ETL 子进程丢失导致 DB 记录永远停留在 running 的架构缺陷。新增优雅关闭（3s 超时）、启动时僵尸清理（仅本机）、interrupted 状态、重新执行按钮（所有历史任务）。

## 根因分析

`asyncio.create_task()` 启动的协程在 uvicorn 重启时被丢弃，`execute()` 的 `finally` 块未执行，DB 中 `task_execution_log` 记录永远停留在 `status='running'`、`finished_at=NULL`。

## 文件变更清单

### 后端
| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `apps/backend/app/services/task_executor.py` | 修改 | 新增 `shutdown()` 优雅关闭方法（terminate→wait→kill）、`recover_stale()` 启动僵尸清理、`execute()` finally 末尾添加 `cleanup()` 防内存泄漏 |
| `apps/backend/app/main.py` | 修改 | lifespan startup 调用 `recover_stale()`，shutdown 调用 `shutdown(timeout=3.0)` |
| `apps/backend/app/routers/execution.py` | 修改 | 新增 `POST /api/execution/{id}/rerun` 端点 |

### 前端
| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `apps/admin-web/src/api/execution.ts` | 修改 | 新增 `rerunExecution()` API 函数 |
| `apps/admin-web/src/pages/TaskManager.tsx` | 修改 | STATUS_COLOR 添加 interrupted、操作列添加重新执行按钮（所有非 running 任务） |

### 数据修复
- 测试库 `bb9a0761` 记录已手动标记为 `interrupted`

## 关键设计决策

1. **优雅关闭 3s 超时**：uvicorn 关闭后子进程无意义，快速终止
2. **僵尸清理仅限本机**：通过 `command LIKE '[hostname]%'` 匹配，多实例安全
3. **interrupted 状态**：区别于 cancelled（用户主动取消）和 failed（执行出错）
4. **rerun 用默认 config**：DB 未存完整 TaskConfig，仅保留 task_codes，用 `api_full` + `increment_only` 默认配置重新执行
5. **cleanup 调用**：execute() finally 末尾释放内存缓冲区，防止长期运行内存泄漏

## 风险评估

- shutdown 超时后强制 kill，不会阻塞 uvicorn 关闭流程
- recover_stale 在 task_queue.start() 之前执行，不会与新任务冲突
- rerun 不保留原始 window/lookback 参数（已知限制，后续可扩展存储完整 config）

## 回滚策略

revert 5 个文件即可。DB 中 interrupted 状态记录无副作用，无需回滚数据。

## 验证步骤

```sql
-- 1. 确认无僵尸任务
SELECT id, status FROM task_execution_log WHERE status = 'running';
-- 预期：0 行（或仅有真正在运行的任务）

-- 2. 确认 bb9a0761 已修复
SELECT id, status, finished_at FROM task_execution_log
WHERE id = 'bb9a0761-95ff-4663-9053-9bb68b2603bb';
-- 预期：status='interrupted', finished_at IS NOT NULL

-- 3. 确认 interrupted 状态存在
SELECT DISTINCT status FROM task_execution_log ORDER BY status;
-- 预期：包含 interrupted
```
