concurrent.interpreters: Interpreter.call() crashes when the result fails to unpickle in the caller
还没有人认领这个 Issue。
- 主要语言
- 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
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 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