- 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。
50 lines
1.8 KiB
Python
50 lines
1.8 KiB
Python
#!/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
|
||
|