# 问题处理：批量监控页任务状态进入即显示「已完成」

> 处理时间：2026-08-21
> 现象报告人：用户（点击「执行全部」跳转批量监控页后）
> 关联文档：`_PRD_批量执行跳转监控页.md`
> 状态：✅ 已修复并部署

---

## 1. 问题现象

用户点击「执行全部」后跳转到 `BatchMonitor.vue` 页面，发现：

- **任务列表**中所有任务状态**立即显示「已完成」**（绿色标签），TPS / 响应时间有数据
- **顶部进度条**却显示「执行中」，`doneCount` / `totalCount` 的比例也可能不对

预期行为：任务进入时应为「等待中」→ 逐个变为「运行中」→「已完成」。

---

## 2. 初步定位

### 2.1 数据流向

```
用户点击「执行全部」
  → Router: POST /api/performance/projects/{id}/run-all
    → service.create_batch_and_start(task_ids, project_id)
      → 1. 创建 batch 记录 (status=running)
      → 2. asyncio.create_task(_run_batch_background(...))
      → 3. 立即返回 batch_id
  → 前端跳转: /performance/batch-monitor?batchId=xxx&projectId=xxx
    → BatchMonitor.onMounted() → loadBatchDetails()
      → GET /api/performance/batch-details/{batch_id}
        → service.get_batch_details() → 读取各 PerformanceTask 的当前 status
```

### 2.2 关键时间差

`create_batch_and_start()` 在**当前 event loop 的同步代码**中创建 batch 记录并启动 `asyncio.create_task()`，然后立即返回。

后台任务 `_run_batch_background()` 创建新的数据库会话、初始化 service、逐个调用 `run_task()` — 这些步骤**需要若干事件循环周期**。

在后台任务尚未执行到第一个 `run_task()` 时，前端已经调用了 `GET /batch-details/{batch_id}`，此时各任务的 `status` 仍然是**上一次执行留下的残留值**。

### 2.3 残留状态来源

`run_project_all()` 的查询条件：

```python
PerformanceTask.status.in_(["pending", "completed", "failed", "cancelled"])
```

即：只有处于这 4 种状态的任务才被纳入本次执行。但**纳入后并不会立即修改任务状态** — 状态修改发生在后续 `run_task()` 中（设为 `running` → `completed/failed`）。

所以，如果上一次批量执行已完成（任务全是 `completed`），本次再点「执行全部」时：

| 时间点 | batch.status | task.status | 说明 |
|--------|-------------|-------------|------|
| API 返回前 | — | completed | 上次残留 |
| API 返回后、后台未启动 | running | completed | batch 已创建，但 task 还没被重置 |
| 后台开始执行 task #1 | running | running（#1）, completed（#2~N） | #1 开始执行，其余还没动 |
| task #1 完成 | running | completed（#1, #2~N） | #2 还没开始 |

**用户进入页面时，恰好命中第 2 行：batch.status=running，但 task.status 全是上一次的 completed。**

---

## 3. 根因分析

**直接原因**：`create_batch_and_start()` 在创建 batch 记录后、启动后台执行前，**没有将相关任务的状态重置为 `pending`**。

**深层原因**：

1. `batch_run_tasks()` 方法本身在 `if not batch_id` 分支中会创建 batch 记录，但不负责重置 task status — 它假设 `run_task()` 会在执行时自动设为 `running`
2. `run_task()` 在执行开始前会检查 `if task.status == "running": raise ValueError("不能重复启动")` — 但不检查也不重置 `completed` / `failed` 状态
3. 新增的 `create_batch_and_start()` 在 `asyncio.create_task()` 之前创建了 batch 记录但**未做任何 task 状态前置处理**

因此，前端在后台任务启动前的窗口期查询到的全是残留状态。

---

## 4. 修复方案

在 `create_batch_and_start()` 中，**提交 batch 记录后、启动后台任务前**，将所有涉及的任务状态重置为 `pending`：

```python
# backend/app/services/performance_service.py — create_batch_and_start()

self.db.add(batch_record)

# 将批次内所有任务状态重置为 pending（避免读到上一次执行的残留 completed/failed 状态）
for task_id in task_ids:
    task = await self.db.get(PerformanceTask, task_id)
    if task and task.status in ("completed", "failed", "cancelled", "pending"):
        task.status = "pending"

await self.db.commit()

# 在后台异步执行
task = asyncio.create_task(...)
```

**设计考量**：

- 只重置 `completed` / `failed` / `cancelled` / `pending` 状态的任务 — **跳过正在 `running` 的任务**，避免干扰其他正在执行的批量
- 重置操作在 batch 记录提交后、后台任务启动前执行，确保前端查询时看到的状态是干净的
- 不修改 `run_task()` 的逻辑（它在执行开始时本来就会设为 `running`），仅做前置清理

### 同时修复的附带问题

`_run_batch_background()` 之前调用 `batch_run_tasks()` 时**未传递 `batch_id`**，导致后台使用 `if not batch_id:` 分支会创建**第二个** batch 记录。已修复为传递 `batch_id`：

```python
await service.batch_run_tasks(
    task_ids=task_ids,
    project_id=project_id,
    batch_id=batch_id,  # 修复：复用已创建的 batch 记录
)
```

---

## 5. 部署与验证

### 5.1 部署步骤

1. 上传修复后的后端文件：
   ```bash
   scp app/services/performance_service.py ubains@192.168.5.60:.../backend/app/services/
   ```
2. 构建前端并上传：
   ```bash
   cd frontend && npm run build
   scp -r dist ubains@192.168.5.60:.../frontend/
   ```
3. 重启后端容器：
   ```bash
   ssh ubains@192.168.5.60 "cd /data/third_party/plat-auto-test/deploy && docker compose restart app"
   ```

### 5.2 验证结果

| 验证项 | 结果 |
|--------|------|
| 容器重启 | ✅ `Container plat-auto-test-app Started` |
| 部署确认（grep task.status = pending） | ✅ 第 1580 行存在 `task.status = "pending"` |
| 前端构建 | ✅ 无 TypeScript 错误 |

### 5.3 修复后预期行为

| 时间点 | batch.status | task.status | 说明 |
|--------|-------------|-------------|------|
| API 返回后（用户跳转到监控页） | running | pending（全部） | ✅ 前置重置完成 |
| 后台开始执行 task #1 | running | running（#1）, pending（#2~N） | #1 开始 |
| task #1 完成 | running | completed（#1）, pending（#2） | 正常流转 |

---

## 6. 经验总结（避免再犯）

1. **异步创建 + 同步返回**的模式下，**必须在返回前完成所有影响前端查询状态的前置操作**。后台任务的执行时机不可靠（取决于 event loop 调度），不能假设「后台会立即开始执行」。
2. **批量执行的状态管理是两层**：batch 记录层（`PerformanceBatchExecution.status`）和任务层（`PerformanceTask.status`）。两层状态必须在同一次 commit 中保持一致，否则前端会看到矛盾。
3. **重置状态时要排除 `running`**：不能无脑全部设为 `pending`，否则会干扰其他正在进行的批量执行。
4. **附带 bug 修复**：`_run_batch_background()` 漏传 `batch_id`，导致创建重复 batch 记录 — 在排查主问题时顺带发现并修复。

---

*本文档由 Claude Code 生成*