Skip to content

fix(general): 恢复脚本直控配置时裸 rmtree 遇占用抛 WinError 32,目录被半删且拷回永远走不到 #563

Description

@qiyinxi

由 Sentry 每日分诊自动建立。Sentry issue: AUTO-MAS-BACKEND-5T / AUTO-MAS-BACKEND-5V / AUTO-MAS-BACKEND-5W
https://auto-mas.sentry.io/issues/AUTO-MAS-BACKEND-5T
https://auto-mas.sentry.io/issues/AUTO-MAS-BACKEND-5V
https://auto-mas.sentry.io/issues/AUTO-MAS-BACKEND-5W

现象

通用脚本在恢复「脚本直控配置」前,会先把用户填的 ConfigPath 整个目录删掉再从快照拷回。删除这一步用的是裸 shutil.rmtree,Windows 上目录里有任一文件被占用就抛 PermissionError: [WinError 32]rmtree 是边遍历边删的,抛出时目录已经被删掉一部分,后面的拷回永远走不到

  • 4 条事件,同一 trace(c351e2b0f8d049039b5f22c1757b7307)、同一用户(CN 南阳)、同一秒,v5.5.0-beta.2(dist 66fdb0c7ba5d)、trigger: manual_taskmode: AutoProxy
  • 该用户的 ConfigPathF:\qq\MAA-v5.9.0-win-x64,即整个 MAA 安装目录。
  • 三条 Sentry issue 是同一次故障沿三条调用路径各报一次,见下。

定位

行号为最新 dev(978c8b30),与线上 v5.5.0-beta.2 事件里的行号逐行一致,未修复。

app/task/general/manager.py:139 _remove_script_config

def _remove_script_config(self) -> None:
    """清理脚本当前配置路径,避免不同来源的目录文件互相残留。"""
    if self.script_config_path.is_dir():
        shutil.rmtree(self.script_config_path)      # <- :142,无 ignore_errors / 无重试
    elif self.script_config_path.exists():
        self.script_config_path.unlink()

app/task/general/manager.py:162 _restore_external_config:先 _remove_script_config():167),再 copytree(temp -> script_config_path):173)。删除抛异常 → 拷回那行不会执行

三个调用点:

调用点 事件
main_task 换用户前恢复直控配置 :270 5V
on_crash 兜底恢复 :376 5V / 5W
final_taskasyncio.shield)收尾恢复 :302 5T / 5W

根因

  1. 删除无容错:142 是裸 rmtree。同一仓库里 OK-NTE 的对应逻辑(app/task/OkNte/manager.py:194/200/202/216)四处 rmtree 全部带 ignore_errors=True,通用脚本这一处是唯一的例外。dev 上目前没有任何 force_rmtree 之类的重试封装(git grep force_rmtree 在 dev 的 app/ 下无命中;引入它的 PR fix(oknte): 修复脚本配置目录含只读文件时备份与复原失败 #456 只改 OK-NTE,仍开着)。
  2. 先删后拷、失败不回滚_restore_external_config 的删除与拷回不是原子的,删除中途失败就停在「用户目录被删了一半、快照还在 temp 里没拷回来」的状态。快照本身是完好的(external_config_snapshot_ready 仍为 True,temp 内容还在),但代码没有任何路径能跳过删除直接拷回。
  3. 失败后连删三次main_task:270 抛出后 → models/task.py:276on_crashon_crash:376 再调一次 _restore_external_config() → 同一个锁、同一行再抛;随后 final_task:302 第三次。占用只要还在,三次都只会继续删、不会恢复。事件里 on_crash 自己也带着 PermissionError 退出,说明崩溃处理链本身被打断了。

不确定的部分(未验证):占用 F:\qq\MAA-v5.9.0-win-x64 的是哪个进程没有直接证据。main_task:270 的恢复发生在运行下一个用户之前,最可能是上一轮的脚本进程句柄尚未释放。

复现与影响

  • 复现:通用脚本配置两个用户、其中至少一个用「脚本直控配置」,ConfigPath 指向一个仍被脚本进程占用的目录(例如脚本自身的安装目录),跑自动代理。
  • 影响:ConfigPath 指向的目录被部分删除且不会恢复。本次事件里该路径是用户的整个 MAA 安装目录。

相关

顺带记录(未由本次事件触发,仅为读代码所见)

GeneralConfig.Script_ConfigPath 默认值为空串且 FileValidator 放行空串(app/models/config.py:3274),而通用脚本的 check()manager.py:69)不校验 ConfigPath。若 ConfigPath 为空,Path("") 等价于 Path(".")is_dir() 为 True,_remove_script_config 会对进程 cwd(main.py 里 chdir 到的安装根)调 rmtree。这条路径没有在 Sentry 里观察到,未验证是否有其它前置拦截,与 #556 属同一类空路径陷阱。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Sentry dailySentry daily issuesbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions