- 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>
5.4 KiB
5.4 KiB
Bug 修复 v4 - 刷新后后端停止问题
修复的问题
刷新后后端直接停止,不继续执行
原因:
- 页面刷新时 WebSocket 断开
handle_disconnect检测到断开且没有其他连接- 设置
stop_flags[f"user:{username}"]为True - 任务检查停止标志时,会查找
stop_flags[task_id]和stop_flags[f"user:{username}"] - 发现用户级别的停止标志,任务停止执行
解决方案:
在 handle_disconnect 中检查是否有通过 REST API 创建的运行中任务。如果有,说明使用轮询模式,不应该停止任务。
修改的文件
server/socket_handlers.py
修改前:
@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) # 清理所有停止标志
修改后:
@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 结构:
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}
}
查找顺序:
- 先查找
stop_flags[client_sid](WebSocket sid 或 task_id) - 再查找
stop_flags[f"user:{username}"](用户级别)
WebSocket 断开流程(修复后)
-
检测断开
- 移除
connection_users[sid] - 检查是否有其他连接
- 移除
-
检查 REST API 任务
- 查询
task_manager.list_tasks(username) - 检查是否有
status == "running"的任务 - 如果有,设置
has_rest_api_task = True
- 查询
-
决定是否停止
- 如果有其他连接 → 不停止
- 如果有 REST API 任务 → 不停止
- 否则 → 停止任务
-
清理停止标志
- 只清理
stop_flags[sid](sid 级别) - 不清理
stop_flags[f"user:{username}"](用户级别) - 避免影响 REST API 任务
- 只清理
页面刷新流程(修复后)
-
WebSocket 断开
- 检测到有运行中的 REST API 任务
- 不设置停止标志
- 任务继续执行
-
页面重新加载
- 加载历史记录
- 恢复任务状态
- 启动轮询
-
任务继续执行
- 后端任务不受影响
- 继续生成事件
- 前端轮询获取新事件
关键改进
1. 检测 REST API 任务
- 在 WebSocket 断开时检查是否有运行中的 REST API 任务
- 如果有,说明使用轮询模式,不应该停止
2. 保护 REST API 任务
- 只有在没有其他连接且没有 REST API 任务时才停止
- 避免 WebSocket 断开影响 REST API 任务
3. 精确清理停止标志
- 只清理 sid 级别的停止标志
- 不清理用户级别的停止标志
- 避免误清理 REST API 任务的停止标志
测试场景
1. 正常发送消息
- 发送消息
- 观察任务是否正常执行
- 观察输出是否正常
2. 刷新页面(任务进行中)
- 任务执行中刷新页面
- 观察后端是否继续执行
- 观察前端是否正常恢复
- 观察新内容是否正常追加
3. 多次刷新
- 任务执行中多次刷新
- 观察每次刷新后任务是否继续
- 观察是否有累积的问题
4. 停止任务
- 点击停止按钮
- 观察任务是否立即停止
- 观察前端状态是否正确
构建
cd static
npm run build
构建成功!
最终效果
- ✅ 刷新后后端继续执行
- ✅ 前端正常恢复状态
- ✅ 新内容正常追加
- ✅ 工具块正确显示
- ✅ 流畅的输出效果(150ms 轮询)
- ✅ 不重复显示内容
现在可以测试了!刷新页面后,后端应该继续执行,前端会正常恢复并显示新内容。