Prhub

#52810 [CI][ROCm] Prevent Git maintenance races during shallow fetches

原始 PR 作者 AndreasKaratzas 合并时间 2026-08-19 06:34 文件变更 2 提交数 1 评论 2 代码增减 +38 / -3

执行摘要

禁用 Git 自动维护,修复 ROCm CI 浅克隆 fetch 竞态

Buildkite 构建 84415 中 ROCm 测试镜像构建在 Docker 构建前失败,报错为 "Deepening history to reach a version tag" 后紧跟 "fatal: shallow file has changed since we read it"。PR body 明确指出根因:git fetch 默认允许自动仓库维护在分离进程中运行,该进程可能活得比发起 fetch 的进程更久,当脚本随后的 deepen 操作读取 .git/shallow 时维护进程并发写入该文件,Git 一致性检查随即中止。同一构建换 agent 重试后历史深挖、镜像构建及 "AMD image smoke OK" 全部通过,确认失败是瞬时竞态而非依赖变更引入的确定性错误。

值得精读,尤其适合 CI/构建基础设施维护者。这是一个“小而完整”的竞态修复样例:问题定位准确(PR body 完整给出了根因链条)、修复方式克制(只改一个集中函数)、测试设计巧妙(用假 git 拦截 argv 验证选项注入位置,把难以稳定复现的并发竞态转化为确定性单测)。重点关注 git_fetch_with_timeout 中数组构造与展开的写法,以及测试中 PATH 替换与断言顺序的手法。

讨论亮点

本 PR 没有实质性的技术讨论:唯一 reviewer 是 khluu,状态为 APPROVED 且无附加评论;claude[bot] 只发送了模板化提示。可以理解为修复方向与实现范围在维护者看来无争议,属于快速合入的 CI 稳定性补丁。值得注意的是没有任何 reviewer 对 --no-auto-maintenance 的 Git 版本依赖(Git 2.10 及以上)或禁用自动维护后的长期副作用提出疑问。

实现拆解

  1. 变更入口:.buildkite/scripts/ci-bake-rocm.sh 中的 git_fetch_with_timeout 函数,该函数是 ROCm 缓存构建设施中所有 Git fetch 的集中入口,默认超时 60 秒(可由 ROCM_CACHE_GIT_FETCH_TIMEOUT 覆盖)。
  2. 核心改动:将该函数改为先构造数组 fetch_command=(git fetch --no-auto-maintenance),再在“有 timeout 命令”与“无 timeout 命令”两条路径中统一用 "${fetch_command[@]}" 展开执行。数组写法规避了参数空格与通配符展开的 shell 陷阱,--no-auto-maintenance 紧跟 fetch 子命令之后,符合 Git 对子命令选项的解析规则,且保留调用方全部原有参数与失败行为。
  3. 测试配套:tests/tools/test_docker_build_metadata_args.py 新增 test_rocm_git_fetch_disables_automatic_maintenance。测试在临时目录构造一个逐行打印全部 argv 的假 git 脚本并通过 PATH 前置替换真实 git,随后 source ci-bake-rocm.sh 并调用 git_fetch_with_timeout --quiet origin HEAD,断言输出严格等于 fetch --no-auto-maintenance --quiet origin HEAD,从参数顺序上锁定了修复行为。
  4. 配套重构:将 ci-bake-rocm.sh 路径抽取为模块级常量 ROCM_CI_BAKE,供既有 test_rocm_ci_base_metadata_inputs_cover_ci_base_files 与新增测试复用,消除重复路径字符串。
文件 模块 状态 重要度
.buildkite/scripts/ci-bake-rocm.sh 构建脚本 modified 3.24
tests/tools/test_docker_build_metadata_args.py 构建参数 modified 5.02

关键符号

git_fetch_with_timeout test_rocm_git_fetch_disables_automatic_maintenance

关键源码片段

.buildkite/scripts/ci-bake-rocm.sh core-logic

核心修复文件。在集中式 Git fetch 帮助函数 git_fetch_with_timeout 中为所有 fetch 调用注入 --no-auto-maintenance,从根上消除浅克隆 deepen 与自动维护进程在 .git/shallow 上的竞态。

git_fetch_with_timeout() {
    local timeout_secs="${ROCM_CACHE_GIT_FETCH_TIMEOUT:-60}"
​
    # 显式禁用自动维护:git fetch 默认可能启动分离进程的 repository maintenance,
    # 该进程可能活得比发起 fetch 的进程更久,并在后续 deepen 操作读取
    # .git/shallow 时并发写入同一文件,触发 "shallow file has changed since we read it"
    # 一致性检查失败。--no-auto-maintenance 是 fetch 的子命令选项,
    # 必须紧跟 fetch 之后、位于调用方追加参数之前。
    local -a fetch_command=(git fetch --no-auto-maintenance)
​
    if command -v timeout >/dev/null 2>&1; then
        # 有 timeout 工具时按配置的超时时间包裹,避免 fetch 长时间卡死 CI
        timeout "${timeout_secs}s" "${fetch_command[@]}" "$@"
    else
        # 无 timeout 环境直接执行,保持原有失败行为不变
        "${fetch_command[@]}" "$@"
    fi
}
tests/tools/test_docker_build_metadata_args.py test-coverage

新增回归测试,用假 git 脚本拦截 argv,验证 --no-auto-maintenance 被准确注入到 fetch 子命令之后,防止未来重构破坏该修复。

def test_rocm_git_fetch_disables_automatic_maintenance(tmp_path: Path) -> None:
    # 用假 git 脚本拦截命令,逐行打印 argv,便于验证参数注入位置
    fake_bin = tmp_path / "bin"
    fake_bin.mkdir()
    git = fake_bin / "git"
    git.write_text('#!/bin/sh\nprintf "%s\\n" "$@"\n')
    git.chmod(0o755)
​
    env = os.environ.copy()
    # 将假 git 所在目录前置到 PATH,让脚本中的 git 解析到 fake
    env["PATH"] = f"{fake_bin}:{env['PATH']}"
    result = subprocess.run(
        [
            "bash",
            "-c",
            'source "$1"; git_fetch_with_timeout --quiet origin HEAD',
            "bash",
            str(ROCM_CI_BAKE),
        ],
        check=True,
        env=env,
        stdout=subprocess.PIPE,
        text=True,
    )
​
    # 关键断言:--no-auto-maintenance 必须紧跟 fetch 子命令之后,
    # 保证调用方追加的参数(如 --quiet origin HEAD)不会被选项解析破坏
    assert result.stdout.splitlines() == [
        "fetch",
        "--no-auto-maintenance",
        "--quiet",
        "origin",
        "HEAD",
    ]

评论区精华

无实质 review 讨论,khluu 直接批准 other

PR 只有 khluu 的批准且无评论,claude[bot] 仅发送模板化提示;修复方向与实现范围没有引发维护者质疑。

结论:快速合入,无未解决疑虑。 · 已解决

风险与影响

  • 环境兼容性:git fetch --no-auto-maintenance 需要 Git 2.10+,vLLM CI 环境(现代 Ubuntu 镜像)满足条件,无旧版本回退问题。
  • 竞态只是规避而非根治:禁用自动维护后浅克隆仓库不会定期清理冗余对象与引用,长期可能积累垃圾对象;但 CI 每次均从远端重新 checkout,仓库生命周期极短,该影响可忽略。
  • 覆盖盲区:新增测试只覆盖了无 timeout 命令的分支(测试环境 PATH 中不包含 timeout),未覆盖 timeout 包裹分支;不过两分支逻辑完全对称,且均由同一 fetch_command 数组展开,风险很低。
  • 回归面:改动被严格限制在 git_fetch_with_timeout 单函数内,不触及推理运行时路径,不改变任何 fetch 调用方的参数语义。

影响范围仅限 ROCm CI 测试镜像构建步骤:消除了 .git/shallow 并发写入导致的偶发构建失败,提升 Buildkite ROCm 作业的稳定性与重试通过率。对最终用户无感知,不涉及推理、模型或 API 行为。对团队而言,新测试为将来修改 CI fetch 逻辑提供了护栏,避免再次踩中选项顺序或参数注入问题;同时为其他平台(如 CPU、XPU)的同类浅克隆 fetch 竞态提供了一个可复制的修复样板。

CI 竞态难以稳定复现 依赖 Git 2.10+ 选项 禁用自动维护的长期副作用

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论