Prhub

#26616 Let unittest._ShouldStop propagate through retry() so subTest+failfast works

原始 PR 作者 ch-wan 合并时间 2026-05-29 07:26 文件变更 1 提交数 1 评论 1 代码增减 +8 / -0

执行摘要

修复 retry() 未传播 _ShouldStop 导致 CI 误报

CI 中 attention-backend 单元测试套件在 -f (failfast) 模式下运行时,任何在 with self.subTest(...) 内的 self.skipTest() 调用(例如 FA3 硬件门控、DSA 的 tilelang/trtllm/aiter 能力门控、Mamba2 树验证、draft-extend 后端不可用等)都会导致 retry()_ShouldStop 误判为可重试异常,最终报告为错误。PR body 详细描述了触发链:SkipTestsubTest.__exit__ 捕获并记录跳过 → 因 result.failfast 为 True 而抛出 _ShouldStopretry() 未识别 → 重试并最终包装为 Exception。

建议精读。此 PR 虽小,但揭示了 unittest 内部 _ShouldStop 信号与重试装饰器交互的微妙问题,对理解 Python 测试框架和 CI 失败调试有参考价值。代码注释清晰,是良好的异常处理实践案例。

讨论亮点

无 review 评论。PR 由作者自行审查合并。

实现拆解

  1. 新增导入:在 python/sglang/srt/utils/common.py 中从 unittest.case 导入 _ShouldStop
  2. 异常链处理:在 retry() 函数的 except SkipTest 分支之后、except Exception 之前新增 except _ShouldStop: raise 分支,使该异常直接穿透重试逻辑,由 unittest 框架的 testPartExecutor 处理。
  3. 注释说明:在新增分支处添加多行注释,解释 _ShouldStop 的语义(由 subTest.__exit__ 在 failfast 模式下抛出,表示测试方法应停止,不是错误,不应重试)。
文件 模块 状态 重要度
python/sglang/srt/utils/common.py 工具函数 modified 6.58

关键符号

retry

关键源码片段

python/sglang/srt/utils/common.py core-logic

唯一修改文件;在 retry() 函数中添加对 _ShouldStop 异常的特殊处理,修复 CI failfast 模式下 subTest+skipTest 导致误报的 bug。

# 新增导入
from unittest.case import _ShouldStop# retry() 函数中的异常处理链
def retry(fn, max_retry, initial_delay=2.0, max_delay=60.0, should_retry=lambda e: True):
    for try_index in itertools.count():
        try:
            return fn()
        except SkipTest:
            # 不重试 skip 的测试,直接透传给 TestCase
            raise
        except _ShouldStop:
            # 新增:`_ShouldStop` 是 `unittest.case` 的私有信号,
            # 当 `subTest.__exit__` 捕获到 `SkipTest` 且 `result.failfast`
            # 为 True (CI 使用 `python3 file.py -f`)时抛出。
            # 它的作用是通知外层 `testPartExecutor` 停止当前测试方法,
            # 不应被重试,也不应被当作 error 报告。
            raise
        except Exception as e:
            traceback.print_exc()
            if try_index >= max_retry:
                raise Exception(f"retry() exceed maximum number of retries.")
            if not should_retry(e):
                raise Exception(f"retry() observe errors that should not be retried.")
            delay = min(initial_delay * (2**try_index), max_delay) * (
                0.75 + 0.25 * random.random()
            )
            logger.warning(
                f"retry() failed once ({try_index}th try, maximum {max_retry} retries). "
                f"Will delay {delay:.2f}s and retry. Error: {e}"
            )
            time.sleep(delay)

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

变更极小(+8 行,仅控制流新增一个异常捕获分支),且异常类型为 unittest 内部私有类,外部不应抛出。风险极低。唯一潜在风险是如果未来 Python 版本移除或重命名 _ShouldStop,但可通过版本兼容性注释缓解。

直接影响所有使用 retry() 的 CI 测试用例,特别是 attention-backend 单元测试套件(27 个文件)。修复后,-f 模式下 subTestskipTest() 将正确报告为跳过而非错误,消除 CI 误报。不影响非 unittest 场景下的 retry() 行为。

极小变更

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论