fix(aibox): Batch N 波次1~2——第八轮审计 T 系列速赢与并发正确性修复

波次1 速赢批(7 项):
- T8 ProcessManager watermark 取号与回显记账原子化(entry.lock 改 RLock)
- T9 LLM 非 200 响应抛出前 close,流式路径清 _current_response
- T13 _sanitize_log_msg 增加裸 sk- key 脱敏,异常构造点统一过脱敏
- T12 流式判定改共享 saw_complete 标志(排空后 empty() 恒 False 死逻辑),
  超长截断前移堵住非流式直发绕过 N8 输出预算
- T16/T17 三处 Popen 显式 encoding=utf-8 + errors=replace
- T18 detach/log_file/force 统一过 _coerce_bool(字符串 "false" 误判)

波次2 并发正确性批(6 项):
- T4 registry 输出队列入口快照,不再向共享工具实例传播(跨会话串扰)
- T5 executor 单槽 _current_process 改令牌多槽注册表,_terminate_tree
  显式传 (proc, pgid),finally 只注销自己槽位(防并发误杀进程组)
- T6 FailoverClient 冷却键改 id(client),与 P6 去重键语义一致
- T7 stream_chat 拆普通方法+内部生成器,消除生成器延迟副作用清除取消标志
- T15 工具结果统一 TOOL_MAX_RESULT_LENGTH 截断(两条分支同口径)
- T24 非流式 execute() timeout 下限夹取 1s(该路径无 3600s 兜底)

测试:532 passed / 4 failed(平台环境项)/ 1 skipped,零回归
审计报告补记 §23 第八轮审计 + §24 Batch N 波次1~2 修复记录
This commit is contained in:
qing committed 2026-08-21 17:33:33 +08:00
1 parent 3e860c16e0
commit 5894e64287
8 files changed
+583 -110

No files matched your search

+25 -13
View File
@@ -1376,13 +1376,20 @@ class DeepSeekChat:
# 队列是否为空),闭包长期持有 output_callback(含 websocket 引用)
# 阻碍 GC,常驻服务下线程与内存持续累积。
drain_stop = threading.Event()
# T12:drain 线程与工具调用后的排空循环并发消费同一队列,complete
# 标记可能被任一方取走——用可变容器共享,两路见到都置位,供
# has_stream_output 判定流式工具(流式已逐行转发,勿重复整发结果)
saw_complete = [False]
def _drain_queue(q=output_queue, cb=output_callback, stop_event=drain_stop):
def _drain_queue(q=output_queue, cb=output_callback, stop_event=drain_stop,
flag=saw_complete):
"""持续排空 output_queue 并转发 line 输出(长时间工具执行期间防 WS 空闲断线)"""
while not stop_event.is_set():
try:
msg_type, content = q.get(timeout=0.1)
if cb and msg_type == "line":
if msg_type == "complete":
flag[0] = True
elif cb and msg_type == "line":
cb("line", str(content) if content is not None else "")
except queue.Empty:
continue
@@ -1423,6 +1430,9 @@ class DeepSeekChat:
)
self.logger.warning(f"编排者误调执行面工具 {tool_name},已拦截并引导改用 create_subagent")
else:
# T12:每个工具调用前重置流式标志(drain 线程与下方排空
# 循环并发消费,任一方见到 complete 都会置位)
saw_complete[0] = False
try:
result_raw = self.tool_registry.execute_tool(tool_name, **args)
# 排空队列中剩余标记(complete 标记供 has_stream_output 判定、
@@ -1430,7 +1440,9 @@ class DeepSeekChat:
while not output_queue.empty():
try:
msg_type, content = output_queue.get_nowait()
if output_callback and msg_type == "line":
if msg_type == "complete":
saw_complete[0] = True
elif output_callback and msg_type == "line":
output_callback("line", str(content) if content is not None else "")
except queue.Empty:
break
@@ -1448,20 +1460,20 @@ class DeepSeekChat:
# 安全转换并清理结果
result = self._sanitize_tool_result(result_raw)
# drain 线程持续转发 line 输出,这里通过 complete 标记判断是否流式工具
has_stream_output = not output_queue.empty()
# T12:流式工具的 line 输出已由 drain 线程逐行转发(registry 侧
# N8 预算已生效),勿再把完整 result 整发——此前排空后判定
# output_queue.empty() 恒为 False,导致重复推送 + 超长结果绕过预算
has_stream_output = saw_complete[0]
# 普通(非流式)工具没有队列输出,直接发送结果
# 长度预算前置:超长结果先替换为提示,再决定是否推送,
# 避免全量 result 经非流式直发路径绕过 N8 输出限制(T12)
if len(result) > MAX_RESULT_LENGTH:
result = f"⚠️ 工具调用结果长度超过 {MAX_RESULT_LENGTH} 字符,已丢弃。请使用更精确的查询获取所需信息。"
# 普通(非流式)工具没有队列输出,直接发送(已截断的)结果
if not has_stream_output and output_callback and result:
output_callback("line", result + "\n")
# 检查结果大小,如果超过阈值,不保留截断内容,仅返回提示信息
if len(result) > MAX_RESULT_LENGTH:
truncated_note = f"⚠️ 工具调用结果长度超过 {MAX_RESULT_LENGTH} 字符,已丢弃。请使用更精确的查询获取所需信息。"
result = truncated_note
if output_callback:
output_callback("line", truncated_note + "\n")
# 结束调用工具时发送换行
if output_callback:
output_callback("line", "\n\n")