☰
CPython 低内存崩溃修复:代码编译、解释器通道与 `_winapi.CreateProcess` 中的 `MemoryError` 规范化
2026/9/25 1:58:16 网站建设 项目流程

CPython 低内存崩溃修复:代码编译、解释器通道与_winapi.CreateProcess中的MemoryError规范化

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

本文基于 CPython 主线仓库中的 Misc/NEWS.d/next/Core_and_Builtins/2026-06-09-10-28-30.gh-issue-151126.DKa6Sl.rst 修复记录,剖析当设备内存耗尽时,代码编译、_interpchannels模块与_winapi.CreateProcess三处可能引发崩溃的路径,以及它们如何改为抛出规范的MemoryError。读完本文,你将理解 CPython 在内存分配失败场景下的错误处理约定、各修复点的底层代码位置,以及如何在低内存环境下验证该行为。

修复背景:内存耗尽时的崩溃风险

在嵌入式设备、容器或长期运行的守护进程中,内存可能在任何时刻被耗尽。CPython 内部大量使用malloc/calloc及自有内存分配器,当分配失败时,C 层代码必须检查返回值并转入 Python 错误处理流程。然而,历史上部分路径在分配失败时未做检查,或直接返回NULL而未设置异常,导致解释器在上层拿到NULL时触发段错误(segfault)而非可捕获的MemoryError。

本次修复(issue gh-issue-151126)针对三类场景:

  • 代码编译阶段(Python/compile.c)的编译器对象分配;
  • 解释器通道模块_interpchannels(Modules/_interpchannelsmodule.c)中的锁与等待项分配;
  • Windows 专属的_winapi.CreateProcess(Modules/_winapi.c)内部缓冲分配。

修复的统一手段是:在分配失败处调用PyErr_NoMemory()(其内部通过_PyErr_NoMemory(tstate)设置MemoryError),确保NULL返回值始终伴随异常,从而让上层以异常而非崩溃的方式失败。

代码编译:编译器对象与作用域单元的分配检查

代码编译由 Python/compile.c 实现。该文件在两处分配失败路径上补充了PyErr_NoMemory():

  1. new_compiler()(约 Python/compile.c#L171-L185):使用PyMem_Calloc(1, sizeof(compiler))分配编译器上下文,若返回NULL立即调用PyErr_NoMemory()并返回NULL。此前若未设置异常,调用方会把NULL当作一般错误处理,在无异常状态下继续解引用,进而可能崩溃。

  2. _PyCompile_EnterScope()(约 Python/compile.c#L599-L609):进入新的作用域单元(struct compiler_unit)时同样使用PyMem_Calloc分配,失败时调用PyErr_NoMemory()并返回ERROR标志。该函数为每个函数、类或模块作用域创建编译单元,是编译多层嵌套代码时的热路径,内存耗尽时极易在此触发问题。

此外,Python/codegen.c 与 Python/assemble.c 等编译链路的其他文件中也已普遍存在PyErr_NoMemory()检查(例如Python/assemble.c#L421、Python/codegen.c#L141-L162),说明编译器整体遵循“分配失败即设异常”的约定,本次修复补全了剩余缺口。

解释器通道:_interpchannels的锁与等待项分配

_interpchannels是 CPython 子解释器(subinterpreter)之间进行通道(channel)通信的内部模块,实现在 Modules/_interpchannelsmodule.c。该模块中大量使用PyThread_allocate_lock()分配线程锁,并在等待队列中管理_waiting_t结构,这些分配在极端内存压力下同样可能失败。

本次修复在该文件的 9 处分配失败点统一补上了PyErr_NoMemory()(分布在 Modules/_interpchannelsmodule.c#L466、#L604、#L683、#L862、#L921、#L1115、#L1314、#L1700、#L1743 等位置)。以等待项初始化为例(Modules/_interpchannelsmodule.c#L461-L475):

static int _waiting_init(_waiting_t *waiting) { PyThread_type_lock mutex = PyThread_allocate_lock(); if (mutex == NULL) { PyErr_NoMemory(); /* 分配失败:设置 MemoryError 而不是静默返回 NULL */ return -1; } *waiting = (_waiting_t){ .mutex = mutex, .status = WAITING_NO_STATUS, }; return 0; }

从源码结构看,_interpchannels中还有更多PyThread_allocate_lock()、动态数组扩容等分配点(文件共 3661 行,PyErr_NoMemory()出现 9 处),本次 NEWS 条目描述的即是这批低内存路径的系统性加固,目标是让通道接收(recv)、发送(send)与等待(waiting)操作在内存不足时以MemoryError优雅失败。

_winapi.CreateProcess:Windows 进程创建前的缓冲分配

_winapi是 CPython 在 Windows 上的进程创建内部模块(Modules/_winapi.c),其CreateProcess包装了 Win32 APICreateProcessW。该函数在真正调用系统 API 之前需要完成多项内存分配:

  • getenvironment()将环境变量映射转换为宽字符wchar_t*环境块;
  • PyUnicode_AsWideCharString()将命令行字符串转为可写的宽字符副本;
  • 属性列表(lpAttributeList)等结构的内存管理。

当这些分配在低内存下失败时,路径直接goto cleanup;若失败发生在未设置异常的分配上,上层就会在无异常状态下继续,最终崩溃。本次修复在这些分配失败点补上了PyErr_NoMemory()(例如 Modules/_winapi.c#L1078、#L1134、#L1195、#L1280、#L1685、#L2027、#L2409),使得CreateProcess在内存耗尽时抛出可捕获的MemoryError,而不是让进程创建代码崩溃。

需要说明的是,CreateProcessW系统调用本身失败(例如可执行文件不存在、权限不足)时,代码走的是另一条路径:调用PyErr_SetFromWindowsErr(GetLastError())抛出对应 Windows 错误(Modules/_winapi.c#L1422-L1425)。本次修复只涉及调用系统 API 之前的内部内存分配失败,两者错误语义互不干扰。

修复的统一模式与验证方式

三处修复遵循同一模式:内存分配 API 返回NULL→ 调用PyErr_NoMemory()→ 返回NULL/错误码。PyErr_NoMemory()是 CPython 中设置MemoryError的标准入口,其底层为_PyErr_NoMemory(tstate)(见 Python/bltinmodule.c#L1588 的引用用法),确保异常与NULL返回值严格配对。这一约定在 CPython 内存错误处理中具有普适性:整个代码库(Python/compile.c、Python/codegen.c、Python/assemble.c、Python/ceval.c、Python/crossinterp.c、Python/codecs.c等)均大量使用PyErr_NoMemory()。

由于修复点位于解释器内部,普通用户难以直接构造内存耗尽场景,但可以通过以下方式验证相关行为:

  1. 单元测试:运行_winapi相关测试验证CreateProcess的异常路径,测试位于 Lib/test/test_winapi.py;
  2. 注入式测试:在支持 fault injection(如LD_PRELOAD拦截malloc返回NULL)的环境中运行 Python,可观察代码编译(compile()内建函数)与子解释器通道操作在低内存下抛出MemoryError而非段错误;
  3. 受限内存运行:使用ulimit -v(Linux)等工具限制虚拟内存后执行嵌套函数编译或_interpchannels通信,验证异常可被try/except MemoryError捕获。

修复意义小结

本次修复消除了三类低内存崩溃路径,将“设备内存耗尽”从解释器段错误转变为标准 Python 异常:

修复点所在文件失败场景
代码编译Python/compile.c编译器对象、作用域单元分配失败
解释器通道Modules/_interpchannelsmodule.c线程锁、等待项等分配失败
进程创建Modules/_winapi.cCreateProcess内部缓冲分配失败

对应用开发者而言,这意味着在内存受限的部署环境中,compile()、子解释器通道通信以及 Windows 进程创建代码可以依赖try/except MemoryError进行优雅降级与资源回收,而不是面对无法捕获的进程崩溃;对 CPython 内核开发者而言,这是一次低内存健壮性(OOM-resilience)的常规加固,与此前代码库中既有的PyErr_NoMemory()约定保持一致。

【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询