Files
qing 2dc79f0637 fix(aibox): Batch N 波次3 平台兼容(T3/T11/T25/T26/T27)
- T3 basic_tools 平台感知引用与 Windows 指引:_quote_arg 按 os.name
  分支(Windows 双引号包裹,拒绝双引号/换行/%变量名% 注入形态);
  ReadFile/WriteFile/ListFiles/Web 四工具 Windows 走 PowerShell/
  curl.exe 等价命令(WriteFile 用 Base64 规避 cmd 换行/转义限制)
- T11 executors 与 process_manager 的 _terminate_tree Windows 分支
  改 taskkill /T /F 递归收割进程树,兜底单进程 kill
- T25 MemoryDatabase 统一 _connect 工厂(busy_timeout=10000),
  7 处直连替换,对齐 MemoDatabase J9
- T26 SkillManager._load_skills 原子替换 self.skills(对齐
  PromptManager M6),reload 期间不再读到空/半截技能表
- T27 atomic_write_text 对 PermissionError 做 3 次退避重试

测试:新增 tests/test_n_fixes.py 30 项;全量 566 passed / 1 failed
(Windows 向 ping 用例,环境项),零回归。审计报告补记 §24.5/§24.6。
2026-08-21 13:56:33 +00:00

50 lines
1.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
common.atomic - 原子写文件工具(J5,审计 2026-08-19)
同目录 mkstemp 临时文件 + fsync + os.replace:进程中断/断电不留半截文件;
mkstemp 随机文件名避免多进程并发写同路径互踩(conversation_history 旧实现
的固定 .tmp 名在并发场景会互相覆盖)。仅依赖标准库,供 common/subsystems
各写路径复用。
"""
import os
import tempfile
import time
def atomic_write_text(path: str, content: str) -> None:
"""原子写文本文件。失败时清理临时文件并抛出原异常。"""
path = os.path.expanduser(path)
directory = os.path.dirname(path) or "."
os.makedirs(directory, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(prefix=".aibox_tmp_", dir=directory)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write(content)
f.flush()
os.fsync(f.fileno())
# T27(审计 2026-08-21):Windows 下目标被其他进程打开(杀毒扫描/
# 编辑器/并发读者)时 os.replace 抛 PermissionError。POSIX 无条件
# 成功不受影响;Windows 做短退避重试,高频写路径(每条消息都原子
# 写 current_conversation.json)一次冲突不再直接丢该条持久化。
last_exc = None
for attempt in range(3):
try:
os.replace(tmp_path, path)
last_exc = None
break
except PermissionError as e:
last_exc = e
if attempt < 2:
time.sleep(0.05 * (attempt + 1))
if last_exc is not None:
raise last_exc
except BaseException:
try:
os.unlink(tmp_path)
except OSError:
pass
raise