86 lines
4.1 KiB
Markdown
86 lines
4.1 KiB
Markdown
# Queue-Runner + Watchdog 重复审查Bug(2026-07-02 实证)
|
|
|
|
## 现象
|
|
Doro反复收到同一份合同的"审查完毕"通知。今天受影响的合同:
|
|
- 夏阳合同:被通知至少2次(可能3次)
|
|
- 家庭医生签约合同:被通知2次(第3次在final_review被手动杀掉)
|
|
|
|
## Bug链条(已验证)
|
|
|
|
```
|
|
1. Queue-runner 启动 → START 合同X → 创建 worker PID → "Waiting for worker..."
|
|
2. Runner 进程异常退出(OOM/信号/shell被杀),但 worker 子进程继续运行
|
|
3. Worker 独立完成整个 workflow(包括 final_review = 私信通知 Doro)
|
|
4. Runner 已死 → 没有执行 "mv $FILE done/" → 文件仍在 queue 目录
|
|
5. Watchdog(20min cron)检测到:runner不在 + done/ < manifest → 重启 runner
|
|
6. 新 runner 看到文件还在 queue → SKIP逻辑只检查 `[ -e "$FILE" ]` → 认为未处理
|
|
7. 新 runner 启动第二个 thread → 从头审查 → final_review 又通知 Doro
|
|
```
|
|
|
|
## 额外失败模式(watchdog恢复旧thread)
|
|
|
|
```
|
|
watchdog.log:
|
|
[2026-07-02 18:40:14] suspended 06FJ42KNSK1ZA767W8AZ6MXJ6C → exec 恢复
|
|
[2026-07-02 18:40:15] idle 06FJ3ZJXR5WHJNPDHF22YYMGCM → exec 续跑
|
|
```
|
|
|
|
Watchdog 恢复处于 idle/suspended 状态的旧 thread,这些 thread 的同名合同可能已被新 runner 完成。
|
|
旧 thread 被唤醒后接着跑完 final_review → 又一次通知。
|
|
|
|
## 今天的实际时间线
|
|
|
|
| 时间(BJT) | 事件 |
|
|
|-----------|------|
|
|
| 02:45 | 第一个runner启动,处理恭兴(cancelled) |
|
|
| 05:00 | Watchdog重启runner → 恭兴(成功) |
|
|
| 06:25 | 肃言完成 |
|
|
| 07:28 | 卫健委完成 |
|
|
| 08:42 | 夏阳 thread#1 启动 (06FJ3ZJXR) |
|
|
| ~11:00 | 夏阳#1 完成(含final_review通知);但runner已死,文件没进done/ |
|
|
| 11:40 | Watchdog重启runner → 夏阳 thread#2 启动 (06FJ58AD) |
|
|
| 12:38 | 夏阳#2 完成(第二次通知);家庭医生签约 thread 启动 (06FJ5NPHT) |
|
|
| 18:40 | Watchdog恢复旧idle thread 06FJ3ZJXR5WHJNPDHF22YYMGCM (夏阳#1) + 06FJ42KNSK (家庭医生#?) |
|
|
| 19:21 | 家庭医生签约交付到Nextcloud |
|
|
| ~21:00 | 06FJ42KNSK 完成 final_review(第2次家庭医生通知) |
|
|
| 21:16 | Watchdog再次重启runner → 家庭医生 thread#2 (06FJ5NPHT) 进入 final_review |
|
|
| 21:30 | 手动 kill -9 杀掉 06FJ5NPHT 的 final_review → 阻止第3次通知 |
|
|
|
|
## 止血SOP
|
|
|
|
当 Doro 报告收到重复通知时:
|
|
|
|
1. **找重复进程**:`ps aux | grep -E "(background-worker|uwf-hermes)" | grep -v grep`
|
|
2. **杀掉重复进程**:`kill -9 <worker-PID> <uwf-hermes-PID>`
|
|
3. **杀掉queue-runner**:`kill <queue-runner-PID>`
|
|
4. **清理queue**:把所有tracker中已completed的文件移入done/
|
|
```python
|
|
import json, os, shutil
|
|
tracker = json.load(open(os.path.expanduser('~/.hermes/data/contract-tracker.json')))
|
|
completed = {c['original_filename'] for c in tracker['contracts'] if c['status'] == 'completed'}
|
|
queue_dir = '/tmp/contract-queue'
|
|
for f in os.listdir(queue_dir):
|
|
if f.endswith(('.doc', '.docx', '.pdf')) and f in completed:
|
|
shutil.move(f'{queue_dir}/{f}', f'{queue_dir}/done/{f}')
|
|
```
|
|
5. **验证**:`find /tmp/contract-queue/ -maxdepth 1 -name '*.doc*'` 应为空
|
|
6. **确认watchdog不会重启**:done/ 文件数 ≥ manifest.txt 行数
|
|
|
|
## 缺失防线(待修复)
|
|
|
|
| 位置 | 应加的检查 |
|
|
|------|-----------|
|
|
| queue-runner START 逻辑 | 启动workflow前查tracker:已completed直接mv到done/ |
|
|
| watchdog 恢复thread逻辑 | resume前查:同filename的tracker记录是否already completed |
|
|
| final_review | 发通知前查:是否24h内已有同合同的通知(防御性去重) |
|
|
|
|
## 关键文件路径
|
|
|
|
- Queue runner: `/home/maggie/.hermes/skills/devops/uwf/scripts/contract-queue-runner.sh`
|
|
- Watchdog: `~/.hermes/scripts/contract-queue-watchdog.sh`
|
|
- Queue dir: `/tmp/contract-queue/` (manifest.txt + done/)
|
|
- Queue log: `/tmp/contract-queue/queue.log`
|
|
- Watchdog log: `/tmp/contract-queue/watchdog.log`
|
|
- Tracker: `~/.hermes/data/contract-tracker.json`
|
|
- Cron jobs: `contract-queue-watchdog` (*/20, job_id:3174518affda), `auto-notify-watchdog` (5min, job_id:63bb31d4f050)
|