python / python/cpython

concurrent.interpreters: Interpreter.call() crashes when the result fails to unpickle in the caller

未关闭
#151,892 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

3.14 3.15 3.16 extension-modules topic-subinterpreters type-bug type-crash
主要语言
Python
星标
77.2k
派生
36k
PR 合并指标
PR 指标待抓取

描述

When Interpreter.call() returns a value, that value is pickled in the callee interpreter and reconstructed (unpickled) in the calling interpreter on the way out of the cross-interpreter session, inside _PyXI_Exit() (Python/crossinterp.c). If the reconstruction step raises — e.g. the returned object's __reduce__ produces a callable that raises during unpickling — the raised exception is left set while _PyXI_Exit() applies its "preserve failure" handling. On a debug build this trips assert(!_PyErr_Occurred(tstate)) and aborts the process (SIGABRT); on a release build the stale exception is clobbered and the resulting error state is wrong.

Reproduction (debug build)
import sys, os, tempfile, importlib
from textwrap import dedent
from concurrent import interpreters

d = tempfile.mkdtemp(); sys.path.insert(0, d)
with open(os.path.join(d, "b.py"), "w") as f:
    f.write(dedent('''
        def _boom():
            raise ValueError("unpickling failed")
        class Bomb:
            def __reduce__(self):
                return (_boom, ())
        def mk():
            return Bomb()
    '''))
m = importlib.import_module("b")
ip = interpreters.create()
ip.prepare_main(_d=d)
ip.exec("import sys; sys.path.insert(0, _d); import b")
ip.call(m.mk)   # debug build: Assertion failed in _PyXI_Exit -> SIGABRT

This affects only the call() path (and any other _PyXI_Enter/_PyXI_Exit-bracketed result-preserve path). Queue/channel get()/recv() and exec() do not go through this path and are unaffected. Since call() runs the caller's own code in the subinterpreter, this is a robustness / crash bug, not a security issue.

Expected: call() should raise a normal Python exception instead of aborting.

Fix

Have the reconstruction-failure branch consume the live exception and propagate it as the failure, mirroring how the active-session _pop_preserved path already routes errors through capture_session_error. With the fix, call() raises NotShareableError with the underlying error reachable as the cause. A self-contained regression test is added in Lib/test/test_interpreters/test_api.py.

Affected versions

main only. The result-preserve machinery (_PyXI_Preserve / _finish_preserved, gh-133484) and the xi_error_* error-handling rewrite (gh-135369) that this touches both landed after the 3.14.0b1 freeze, so the affected code path does not exist on 3.14 or earlier.

Related

A separate, distinct crossinterp NULL dereference on the queue/channel receive path is tracked in #151862.

Linked PR

A PR follows.

Linked PRs
  • gh-151893

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 Python/crossinterp.c 中的 _PyXI_Exit() 开始,将重建失败的处理与活动会话的 _pop_preserved 路径和 capture_session_error 进行比较。然后阅读 Lib/test/test_interpreters/test_api.py 中的回归测试并运行该测试;当 call() 以底层错误为其 cause 引发 NotShareableError,而不是中止时,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
c, python
领域
compilers
Issue 类型
缺陷
难度
3/5
预计耗时
1-2 天
活跃度
停滞
描述清晰度
描述清楚
新手友好度
25/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。