# 问题处理文档 - 退出登录步骤超时与后端僵尸进程

> **文档类型**: 问题处理文档
> **创建日期**: 2026-07-16
> **作者**: czj
> **优先级**: P0
> **状态**: 待修复

---

## 一、问题描述

### 1.1 现象

批量执行会议管理模块 6 个用例时，前 5 个通过，**末用例"预定数据页面访问验证"的"退出登录"步骤超时失败**，通过率 83.33%。

退出登录步骤定义：

```json
{"order": 3, "name": "退出登录", "action": "navigate", "params": {"url": "https://192.168.5.44/#/login"}}
```

### 1.2 关键异常

1. **独立 Python 脚本**直接调用 `PlaywrightExecutor` → 退出登录**完全通过**
2. **通过后端 API 触发**执行 → 退出登录**失败**
3. 代码中添加了 `logout_debug.log` 写入调试，但 API 触发执行后该文件**根本没创建**
4. 说明 navigate 分支的 `reset_login_state` 代码在实际后端进程中**没执行到**

---

## 二、根因分析

### 2.1 直接原因：后端运行旧代码

| 验证项 | 结果 |
|--------|------|
| 源代码中 `reset_login_state` 分支是否存在 | ✅ 存在（第 710-726 行） |
| `logout_debug.log` 是否生成 | ❌ 未生成 |
| 独立脚本执行退出登录 | ✅ 通过 |
| API 触发执行退出登录 | ❌ 失败 |

**结论**：源代码是新的，但实际运行的后端进程加载的是**旧代码**。

### 2.2 根本原因：8001 端口僵尸进程

通过排查发现：

```
8001 端口 LISTENING 进程：
PID 25864 → tasklist 查不到（已死）
PID 25884 → tasklist 查不到（已死）
PID 24140 → tasklist 查不到（已死）
PID 2456  → tasklist 查不到（已死）
PID 5620  → tasklist 查不到（已死）
PID 26524 → tasklist 查不到（已死）
PID 23672 → tasklist 查不到（已死）
PID 15316 → tasklist 查不到（已死）

8002 端口 LISTENING 进程：
PID 28076 → python.exe uvicorn --port 8002（正在运行，新代码）
```

- 8001 端口有 **8 个僵尸进程**（父进程已死，子进程通过 `multiprocessing.spawn` 成为孤儿）
- 这些僵尸进程仍在 8001 端口 LISTENING，持有旧代码的 socket
- 前端 vite.config.ts 代理指向 `http://localhost:8001`
- 新启动的 uvicorn 因端口被占无法绑定 8001，用户可能又启动了 8002 端口
- **实际通过前端操作触发执行时，请求打到了 8001 的旧代码进程**

### 2.3 僵尸进程产生的原因

多次使用 `uvicorn --reload` 启动后端时：
1. uvicorn 主进程启动 → 加载代码 → 监听 8001
2. `--reload` 检测到文件变化 → 旧主进程退出 → 新主进程尝试绑定 8001
3. 旧主进程的子进程（multiprocessing.spawn）未正确清理 → 成为孤儿进程
4. 孤儿进程的 socket 仍在 8001 上 LISTENING → 新进程绑定失败 → 新进程也退出
5. 循环往复，僵尸越积越多

### 2.4 退出登录代码逻辑本身的问题

即使僵尸进程问题解决后，退出登录的代码也有以下需优化之处：

| # | 问题 | 位置 | 说明 |
|---|------|------|------|
| 1 | 调试代码残留 | 第 712-723 行 | `logout_debug.log` 写入是调试代码，不应留在生产代码中 |
| 2 | 退出登录仅依赖 navigate 分支 | 第 710-726 行 | 只有 URL 含 `login` 时才走 reset 分支，不够健壮 |
| 3 | 智能跳过逻辑与退出登录耦合 | 第 697-705 行 | 靠步骤名含"退出"来区分，命名不规范会导致逻辑错误 |

---

## 三、修复方案

### 3.1 方案：清理僵尸进程 + 统一端口 + 代码优化（推荐）

**步骤一：清理所有僵尸 python 进程**

杀掉所有 8001 端口的僵尸进程，确保端口完全释放。

**步骤二：统一后端端口为 8001**

在 8001 端口启动后端（不用 `--reload`），前端代理无需修改。

**步骤三：清理调试代码 + 优化退出登录逻辑**

1. 移除 `logout_debug.log` 调试代码
2. 退出登录步骤增加 `logout` 动作类型支持（或用 step.params 中的标志位）
3. 智能跳过逻辑改为基于步骤 params 而非步骤名

### 3.2 不采用的方案

| 方案 | 原因 |
|------|------|
| 前端改代理到 8002 | 治标不治本，8001 僵尸仍占资源 |
| 重启电脑 | 太重，可精确杀进程解决 |
| 继续用 --reload | 正是 --reload 导致僵尸进程，不应再用 |

---

## 四、验收标准

| 测试项 | 预期结果 |
|--------|----------|
| 8001 端口无僵尸进程 | `netstat -ano | grep 8001` 只有一个 LISTENING |
| 后端在 8001 端口正常启动 | `curl localhost:8001/health` 返回 healthy |
| 会议管理 6 用例批量执行 | 退出登录步骤通过，6/6 全部 passed |
| 代码中无调试残留 | 无 `logout_debug.log` 写入代码 |
| 前端正常访问 | `localhost:3000` 功能正常 |

---

## 五、相关文件

- `backend/app/executors/playwright_executor.py` — 执行器（退出登录逻辑、调试代码）
- `frontend/vite.config.ts` — 前端代理配置（指向 8001）
- `backend/app/main.py` — 后端入口（Windows ProactorEventLoop 配置）

---

*本文档由 Claude Code 生成，遵循项目问题处理文档规范。*
