Prhub

#3246 fix(workers): avoid forwarding attached Python entrypoints

原始 PR 作者 bojiang-li 合并时间 2026-09-15 07:31 文件变更 2 提交数 1 评论 2 代码增减 +42 / -4

执行摘要

避免 worker 启动转发父进程 Python 入口点

PR body 明确指出问题:当 Miles 进程以 attached Python entrypoint(如 python -cCODE 或 python -mmodule)启动时,python_argv_prefix() 会把该入口点转发到 worker 启动命令中,导致附加的 worker 模块变成父入口点的参数而不是 Python 实际执行的程序。聚簇形式(如 -ucCODE)同样受影响,且 -uW ignore / -uX dev 这类带值短选项会丢失独立的选项值。因此需要扫描短选项簇直到命令/模块选择器,保留前面的解释器标志,并把 -W / -X 选项的剩余部分视为值。

值得精读。python_argv_prefix() 是解释器 argv 解析的典型边界场景:短选项可聚簇、部分选项带值、attached selector 需要与普通标志区分。该 PR 用逐字符簇扫描 + for...else 结构以少量代码覆盖了这些边界,并配套真实子进程回归测试而非仅断言字符串,测试设计值得借鉴。建议重点阅读 python_argv_prefix() 的扫描逻辑与新增参数化测试。

讨论亮点

该 PR 由 AI 辅助完成(PR body 声明 Codex 检查源码、实现变更并运行检查,未声称人工 review)。claude[bot] 首次评论指出 fork PR 默认禁用自动审查;维护者 guapisolo 触发 @claude review 后,claude[bot] 报告 Code review found no issues,随后 guapisolo 直接批准合并。无实质设计争论,也未留下未解决疑虑。

实现拆解

  1. 常量收敛:在 miles/utils/workers/argv_utils.py 中,将 _INTERPRETER_FLAGS_TAKING_A_VALUE 和 _INTERPRETER_FLAGS_ENDING_THE_PREFIX 两个整 token 集合替换为 _INTERPRETER_SHORT_FLAGS_TAKING_A_VALUE = frozenset({'X', 'W', 'Q'}),为字符级扫描做准备。
  2. 核心扫描逻辑:重写 python_argv_prefix()。对不以 -- 开头的 token 逐字符枚举 token[1:]:遇到 c 或 m 时,把 selector 之前的标志(如 -u)加入前缀并立即返回,不转发 selector 及其后的代码/模块名;遇到 X / W / Q 时,整个 token 作为选项保留,若该字符位于 token 末尾则再取下一个 token 作为值,否则剩余字符视为值并停止扫描。普通标志原样保留。
  3. 长选项与终止符:--check-hash-based-pycs 仍单独携带下一个值;- 与 -- 继续作为前缀终止符,长选项不参与簇扫描。
  4. 测试配套:在 tests/fast/utils/workers/test_argv_utils.py 新增 3 个参数化测试:test_attached_command_does_not_replace_the_child_entrypoint(覆盖 -c、-uc、-OOuc,并用返回的前缀真实启动子进程验证 child entrypoint 可执行)、test_attached_module_is_not_forwarded(覆盖 -m、-um)、test_short_option_values_are_not_scanned_as_entrypoint_flags(覆盖 -Wignore::DeprecationWarning、-Ximporttime、-uW ignore、-uX dev)。
  5. 验证情况:PR body 报告在 macOS、Python 3.13.2 下,修复前 7 个新回归用例失败、2 个对照组通过;修复后 67 个用例全部通过,Ruff、Black、isort 与 git diff --check 均通过。完整 CPU CI 与 GPU/Ray 端到端训练未在本地运行。
文件 模块 状态 重要度
miles/utils/workers/argv_utils.py 参数解析 modified 6.18
tests/fast/utils/workers/test_argv_utils.py 回归测试 modified 5.79

关键符号

python_argv_prefix

关键源码片段

miles/utils/workers/argv_utils.py core-logic

核心修复文件。重写 `python_argv_prefix()` 的短选项解析路径,正确处理 attached `-c` / `-m` selector 与 `-W` / `-X` / `-Q` 带值选项,避免 worker 子进程继承父进程入口点。

def python_argv_prefix() -> list[str]:
    # 根据 sys.orig_argv 重建可安全追加模块入口的解释器前缀,
    # 调用方会继续追加 -m <worker_module> 启动子进程。
    # 必须保留解释器标志(如 -O、-u),但不能转发父进程自己的
    # attached 入口点(如 -cCODE 或 -mmodule),否则子模块会变成
    # 父入口点的普通参数。
    prefix = [sys.executable]
    tokens = iter(sys.orig_argv[1:])
    for token in tokens:
        # - 表示从 stdin 读取,-- 结束选项解析,两者都终止前缀。
        if not token.startswith('-') or token in ('-', '--'):
            break
        if not token.startswith('--'):
            # 短选项可以聚簇,例如 -OOu 等价于 -O -O -u。
            # 逐字符扫描,直到发现命令 / 模块选择器或带值短选项。
            for index, flag in enumerate(token[1:], start=1):
                if flag in ('c', 'm'):
                    # 遇到 selector:保留其前面的解释器标志(如 -u),
                    # 丢弃 selector 本身及其后的代码 / 模块名。
                    if index > 1:
                        prefix.append(token[:index])
                    return prefix
                if flag in _INTERPRETER_SHORT_FLAGS_TAKING_A_VALUE:
                    # -W / -X / -Q 的取值可能连写(如 -Wignore),
                    # 也可能以独立 token 给出(如 -uW ignore)。
                    # 整个 token 都属于解释器选项,原样保留。
                    prefix.append(token)
                    if index == len(token) - 1:
                        prefix.append(next(tokens))
                    # 剩余字符属于选项值,不再视为短标志。
                    break
            else:
                # 整个 token 都是普通解释器标志,原样保留。
                prefix.append(token)
            continue
        # 长选项不做聚簇扫描,仅 --check-hash-based-pycs 需要带走一个值。
        prefix.append(token)
        if token == '--check-hash-based-pycs':
            prefix.append(next(tokens))
    return prefix

评论区精华

自动代码审查与批准 other

claude[bot] 首次评论说明 fork PR 默认禁用自动审查,需维护者触发;guapisolo 评论 `@claude review` 后,claude[bot] 报告 `Code review found no issues`,随后 guapisolo 直接 APPROVED。PR body 也声明由 Codex 辅助实现并运行检查,且未声称完整 CI 与 GPU E2E 验证。

结论:无设计争议;按当前实现合并,完整 CI 验证留待合并后执行。 · 已解决

风险与影响

核心风险在于 python_argv_prefix() 是 worker 启动链路的基础工具,改动会影响所有经它重建解释器前缀的场景:

  • 回归风险:PR body 明确说明完整 CPU CI 与 GPU/Ray E2E 未在本地运行,worker 启动关联的 rollout 路径需要 CI 兜底;
  • 边界风险:字符级扫描依赖 Python 短选项聚簇规则,-W / -X / -Q 的取值若以 c 或 m 开头(如 -Wc...)会先命中 W 分支而不会误判为 selector,但任何未预料的取值格式都可能改变前缀输出;
  • 兼容性:-Q 为历史遗留选项,保留在集合中无实际影响;--check-hash-based-pycs 行为不变;
  • 异常行为:若 -W / -X 等带值选项缺少独立值,next(tokens) 仍会抛 StopIteration,与改动前一致,未新增风险;
  • 性能无影响,前缀解析仅在进程启动时执行一次。

影响范围集中在 worker 启动路径:使用 python -c ... / python -m ... 包装启动 Miles 的用户,此前 worker 会错误执行父入口点或把 worker 模块当作父程序参数,修复后 worker 能正确加载自身模块。对常规 python script.py 或 python -m miles... 启动方式,前缀输出与改动前一致。团队影响:改动小、测试针对性强,后续维护成本低;但需注意该工具函数被多个启动入口复用,合并后应让完整 CI 覆盖验证。

worker 启动路径变更 完整 CI 与 GPU E2E 未验证 argv 解析边界改动

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论