- Remove direct task.cancel() calls, use stop flag instead - Monitor stop flag every 100ms during tool execution - Cancel tool task immediately when stop flag is detected - Return "命令执行被用户取消" as tool result with role=tool - Save cancellation result to conversation history - Prevent abrupt task termination, allow graceful shutdown Changes: - server/socket_handlers.py: Comment out pending_task.cancel() - server/tasks.py: Comment out entry['task'].cancel() - server/chat_flow_tool_loop.py: Add stop flag monitoring loop Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
187 lines
5.4 KiB
Markdown
187 lines
5.4 KiB
Markdown
# Bug 修复 v4 - 刷新后后端停止问题
|
||
|
||
## 修复的问题
|
||
|
||
### 刷新后后端直接停止,不继续执行
|
||
**原因**:
|
||
1. 页面刷新时 WebSocket 断开
|
||
2. `handle_disconnect` 检测到断开且没有其他连接
|
||
3. 设置 `stop_flags[f"user:{username}"]` 为 `True`
|
||
4. 任务检查停止标志时,会查找 `stop_flags[task_id]` 和 `stop_flags[f"user:{username}"]`
|
||
5. 发现用户级别的停止标志,任务停止执行
|
||
|
||
**解决方案**:
|
||
在 `handle_disconnect` 中检查是否有通过 REST API 创建的运行中任务。如果有,说明使用轮询模式,不应该停止任务。
|
||
|
||
## 修改的文件
|
||
|
||
### server/socket_handlers.py
|
||
|
||
**修改前**:
|
||
```python
|
||
@socketio.on('disconnect')
|
||
def handle_disconnect():
|
||
username = connection_users.pop(request.sid, None)
|
||
has_other_connection = False
|
||
if username:
|
||
for sid, user in connection_users.items():
|
||
if user == username:
|
||
has_other_connection = True
|
||
break
|
||
|
||
task_info = get_stop_flag(request.sid, username)
|
||
if isinstance(task_info, dict) and not has_other_connection:
|
||
task_info['stop'] = True # 设置停止标志
|
||
# ... 取消任务
|
||
|
||
clear_stop_flag(request.sid, None) # 清理所有停止标志
|
||
```
|
||
|
||
**修改后**:
|
||
```python
|
||
@socketio.on('disconnect')
|
||
def handle_disconnect():
|
||
username = connection_users.pop(request.sid, None)
|
||
has_other_connection = False
|
||
if username:
|
||
for sid, user in connection_users.items():
|
||
if user == username:
|
||
has_other_connection = True
|
||
break
|
||
|
||
# 检查是否有通过 REST API 创建的运行中任务
|
||
has_rest_api_task = False
|
||
if username and not has_other_connection:
|
||
try:
|
||
from .tasks import task_manager
|
||
running_tasks = [t for t in task_manager.list_tasks(username) if t.status == "running"]
|
||
if running_tasks:
|
||
has_rest_api_task = True
|
||
debug_log(f"[WebSocket] 用户 {username} 有运行中的 REST API 任务,不停止")
|
||
except Exception as e:
|
||
debug_log(f"[WebSocket] 检查 REST API 任务失败: {e}")
|
||
|
||
task_info = get_stop_flag(request.sid, username)
|
||
# 只有在没有其他连接且没有 REST API 任务时才停止
|
||
if isinstance(task_info, dict) and not has_other_connection and not has_rest_api_task:
|
||
task_info['stop'] = True
|
||
# ... 取消任务
|
||
|
||
# 清理停止标志(只清理 sid 级别的,不清理 user 级别的)
|
||
if request.sid in stop_flags:
|
||
stop_flags.pop(request.sid, None)
|
||
```
|
||
|
||
## 工作流程
|
||
|
||
### 停止标志机制
|
||
|
||
**stop_flags 结构**:
|
||
```python
|
||
stop_flags = {
|
||
"client_sid": {"stop": bool, "task": asyncio.Task, "terminal": WebTerminal},
|
||
"user:{username}": {"stop": bool, "task": asyncio.Task, "terminal": WebTerminal},
|
||
"task_id": {"stop": bool, "task": None, "terminal": None}
|
||
}
|
||
```
|
||
|
||
**查找顺序**:
|
||
1. 先查找 `stop_flags[client_sid]`(WebSocket sid 或 task_id)
|
||
2. 再查找 `stop_flags[f"user:{username}"]`(用户级别)
|
||
|
||
### WebSocket 断开流程(修复后)
|
||
|
||
1. **检测断开**
|
||
- 移除 `connection_users[sid]`
|
||
- 检查是否有其他连接
|
||
|
||
2. **检查 REST API 任务**
|
||
- 查询 `task_manager.list_tasks(username)`
|
||
- 检查是否有 `status == "running"` 的任务
|
||
- 如果有,设置 `has_rest_api_task = True`
|
||
|
||
3. **决定是否停止**
|
||
- 如果有其他连接 → 不停止
|
||
- 如果有 REST API 任务 → 不停止
|
||
- 否则 → 停止任务
|
||
|
||
4. **清理停止标志**
|
||
- 只清理 `stop_flags[sid]`(sid 级别)
|
||
- 不清理 `stop_flags[f"user:{username}"]`(用户级别)
|
||
- 避免影响 REST API 任务
|
||
|
||
### 页面刷新流程(修复后)
|
||
|
||
1. **WebSocket 断开**
|
||
- 检测到有运行中的 REST API 任务
|
||
- 不设置停止标志
|
||
- 任务继续执行
|
||
|
||
2. **页面重新加载**
|
||
- 加载历史记录
|
||
- 恢复任务状态
|
||
- 启动轮询
|
||
|
||
3. **任务继续执行**
|
||
- 后端任务不受影响
|
||
- 继续生成事件
|
||
- 前端轮询获取新事件
|
||
|
||
## 关键改进
|
||
|
||
### 1. 检测 REST API 任务
|
||
- 在 WebSocket 断开时检查是否有运行中的 REST API 任务
|
||
- 如果有,说明使用轮询模式,不应该停止
|
||
|
||
### 2. 保护 REST API 任务
|
||
- 只有在没有其他连接且没有 REST API 任务时才停止
|
||
- 避免 WebSocket 断开影响 REST API 任务
|
||
|
||
### 3. 精确清理停止标志
|
||
- 只清理 sid 级别的停止标志
|
||
- 不清理用户级别的停止标志
|
||
- 避免误清理 REST API 任务的停止标志
|
||
|
||
## 测试场景
|
||
|
||
### 1. 正常发送消息
|
||
- 发送消息
|
||
- 观察任务是否正常执行
|
||
- 观察输出是否正常
|
||
|
||
### 2. 刷新页面(任务进行中)
|
||
- 任务执行中刷新页面
|
||
- **观察后端是否继续执行**
|
||
- 观察前端是否正常恢复
|
||
- 观察新内容是否正常追加
|
||
|
||
### 3. 多次刷新
|
||
- 任务执行中多次刷新
|
||
- 观察每次刷新后任务是否继续
|
||
- 观察是否有累积的问题
|
||
|
||
### 4. 停止任务
|
||
- 点击停止按钮
|
||
- 观察任务是否立即停止
|
||
- 观察前端状态是否正确
|
||
|
||
## 构建
|
||
|
||
```bash
|
||
cd static
|
||
npm run build
|
||
```
|
||
|
||
构建成功!
|
||
|
||
## 最终效果
|
||
|
||
- ✅ 刷新后后端继续执行
|
||
- ✅ 前端正常恢复状态
|
||
- ✅ 新内容正常追加
|
||
- ✅ 工具块正确显示
|
||
- ✅ 流畅的输出效果(150ms 轮询)
|
||
- ✅ 不重复显示内容
|
||
|
||
现在可以测试了!刷新页面后,后端应该继续执行,前端会正常恢复并显示新内容。
|