- 进程终止:Windows 用 CTRL_BREAK + taskkill /F /T 替代不存在的 killpg/SIGKILL, 超时/取消全路径有界;_is_pid_alive 改 ctypes(原 os.kill(pid,0) 在 Windows 会真杀进程); reader 任务 finally 收口,消除 Task was destroyed but it is pending / unclosed transport - 子智能体调度链:_run_coro 超时 60s + 失败 cancel/close 防幽灵协程;create 改先调度后提交、 失败回滚;state 归属守卫 + 新建 120s 宽限期防误标已终止;_ensure_event_loop 加锁; 子智能体 todo 改 per-agent 隔离存储,不再串到主智能体前端 - 子智能体执行环境(execution_env_text.py):创建/切换/恢复三时点注入环境说明, Windows 下 sandbox=WSL2 bash、direct=cmd;inject_notification 纯通知不触发新一轮工作, tool 序列中延迟到安全点 flush;传统子智能体按既定语义不通知 - 编码:git 侧边栏 subprocess 显式 utf-8 + errors=replace(修中文 Windows GBK _readerthread 崩溃导致侧边栏空白);check_environment/install_package 用 self.python_cmd - 安全/路径:permission.py Windows deny 列表生效 + 驱动器根拦截;路径比较统一 normcase; /tmp 白名单平台分流;正斜杠判断归一化;os-release 平台守卫;PowerShell -ExecutionPolicy Bypass - 稳定性:任务事件轮询持锁快照(修 deque mutated during iteration); 原子写新增 replace_with_retry(修 WinError 5/32 瞬时持锁); 共享文件兜底链 symlink→os.link 硬链接→copy2 - 部署:新增 _bootstrap.bat / setup.bat / start.bat Windows 启动脚本
47 lines
1.5 KiB
Python
47 lines
1.5 KiB
Python
"""原子写文件工具:temp file + os.replace 的 Windows 加固版。
|
||
|
||
背景:Windows 上目标文件被任何句柄以默认共享模式打开时(msvcrt 默认共享读/写,
|
||
不含 FILE_SHARE_DELETE),os.replace 会抛 WinError 5(拒绝访问);
|
||
并发写入者之间则可能抛 WinError 32(共享冲突)。杀毒软件实时扫描、
|
||
Windows Search 索引、同进程并发的读取线程都会造成这类短暂持锁。
|
||
POSIX 下 rename 不受打开句柄影响,无此问题。
|
||
|
||
对策:仅对这两种 winerror 做短退避重试;其他错误立即抛出。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import time
|
||
from pathlib import Path
|
||
from typing import Union
|
||
|
||
# ERROR_ACCESS_DENIED / ERROR_SHARING_VIOLATION
|
||
_RETRY_WINERRORS = {5, 32}
|
||
|
||
PathLike = Union[str, Path]
|
||
|
||
|
||
def replace_with_retry(
|
||
src: PathLike,
|
||
dst: PathLike,
|
||
*,
|
||
attempts: int = 6,
|
||
initial_delay: float = 0.05,
|
||
) -> None:
|
||
"""os.replace 的 Windows 加固版:对瞬时持锁做指数退避重试。
|
||
|
||
attempts 为总尝试次数(含首次),退避序列默认 0.05/0.1/0.2/0.4/0.8s,
|
||
最坏情况约 1.55s。最后一次失败时原样抛出该 OSError。
|
||
"""
|
||
delay = initial_delay
|
||
total = max(1, int(attempts))
|
||
for i in range(total):
|
||
try:
|
||
os.replace(str(src), str(dst))
|
||
return
|
||
except OSError as exc:
|
||
if getattr(exc, "winerror", None) not in _RETRY_WINERRORS or i == total - 1:
|
||
raise
|
||
time.sleep(delay)
|
||
delay = min(delay * 2, 0.8)
|