
CPython 启动路径解析引擎 getpath磁盘空间耗尽时的崩溃修复与 MemoryError 优雅降级【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文围绕 CPython 仓库 Misc/NEWS.d 中一条针对getpath.c的缺陷修复记录展开深入解析解释器启动阶段路径探测子系统的内存分配机制说明当设备剩余内存不足时该模块如何从可能崩溃变为正确抛出 MemoryError。读完本文你将理解 getpath 的内部架构、PyMem 分配器在解释器早期初始化中的作用以及内存分配失败时正确的错误传播方式并能在自己的 C 扩展中复刻同样的健壮性处理。修复记录原文与问题背景这条变更记录位于 Misc/NEWS.d/next/Core_and_Builtins/2026-06-17-16-26-45.gh-issue-151126.Yc_6wC.rst属于 CPython 标准的 news 条目格式Misc/NEWS.d下按Core_and_Builtins等类别组织每条对应一个 gh-issue 编号其全文如下Avoid possible crash ingetpath.cwhere a device has no memory left. Now it properly raises a :exc:MemoryError. Patch by Ivy Xu.翻译过来即修复了getpath.c中当设备内存耗尽时可能发生的崩溃现在会正确抛出MemoryError异常。贡献者为 Ivy Xu对应 GitHub issue 编号 gh-issue-151126。这条记录触及一个非常容易被忽略、但对解释器稳定性至关重要的场景Python 进程启动的极早期阶段。此时解释器尚未完成自举任何未处理的错误都可能直接导致进程崩溃或行为异常而不是像运行期那样抛出一个可捕获的 Python 异常。getpath解释器启动路径探测子系统getpath.cModules/getpath.c是 CPython 在Modules目录下的核心模块负责在解释器初始化阶段计算并设置一系列关键路径例如可执行文件python二进制自身的真实路径标准库stdlib与纯 Python 模块所在目录平台特定的platstdlib目录基于虚拟环境标记如pyvenv.cfg、._pth文件识别并修正路径。该模块采用Python 逻辑 C 原语混合架构路径计算的主流程以 Python 脚本形式编写在 Modules/getpath.py 中其中通过一组 C 实现的辅助函数与底层交互。这组辅助函数通过 getpath_methods 方法表暴露给 Python 层包括abspath、basename、dirname、isfile、isdir、readlines、realpath等对应 getpath.py 顶部注释中列出的同名接口约定# isfile(path) -- path exists and is a file # isxfile(path) -- path exists and is an executable file # joinpath(*paths) -- combine the paths # readlines(path) -- a list of each line of text in the UTF-8 encoded file # realpath(path) -- resolves symlinks in pathPython 层对 C 原语的使用痕迹清晰可见例如在虚拟环境标记与._pth文件的读取中反复调用readlines()见 getpath.py 与 getpath.py。因此C 层任何一处不健壮的内存处理都会波及整个启动流程。内存耗尽崩溃的根因分析崩溃点一读取启动配置文件时的堆分配getpath_readlines()Modules/getpath.c负责以只读二进制方式打开文件并逐行读取供 Python 层的readlines原语使用。它的内存分配链条如下用PyList_New(0)创建返回列表getpath.c用PyMem_Malloc(MAX_FILE)分配 32 KBMAX_FILE 32 * 1024的临时缓冲区getpath.c用_Py_DecodeUTF8_surrogateescape()将缓冲区内容按 UTF-8surrogateescape 错误处理解码为宽字符字符串getpath.c逐行切分后转换为 Python 字符串对象并追加进列表。在上述第 2、3 步中一旦PyMem_Malloc或_Py_DecodeUTF8_surrogateescape因设备可用内存不足返回 NULL旧代码可能未检查返回值就直接使用空指针从而触发段错误等未定义行为进程在启动阶段崩溃。本次修复正是为这些分配点补上了 NULL 检查并调用PyErr_NoMemory()设置MemoryError后返回 NULL。崩溃点二文件过大时的显式越界防护同一函数中还存在另一类防护当读取的字节数cb MAX_FILE时说明文件超过 32 KB 的上限此时通过PyErr_SetString(PyExc_MemoryError, cannot read file larger than 32KB during initialization)显式抛出MemoryErrorgetpath.c。这一分支与本次修复同源共同保证readlines在异常情况下不会对未终止或越界的缓冲区执行buffer[cb] \0之类的写入。崩溃点三路径规范化与符号链接解析中的分配除文件读取外getpath 的路径处理函数也存在多处内存分配失败风险本次修复一并覆盖了这些点典型场景包括_Py_join_relfile()中拼接多个路径片段时对每个片段调用PyMem_Malloc失败时PyErr_NoMemory()并释放已分配资源getpath.cgetpath_realpath()在通过_Py_wreadlink循环解析符号链接、以及PyUnicode_AsWideCharString/Py_EncodeLocale转换路径时对分配失败统一以PyErr_NoMemory()响应getpath.c。这些处理遵循同一种错误传播契约分配失败时立即设置异常、清理已获取资源、返回 NULL由调用方逐层向上传递。修复后的完整错误处理契约将本次修复涉及的检查点汇总可以看到 getpath 的内存错误处理遵循一套统一规范分配/解码点失败信号处理方式PyMem_Malloc分配路径片段数组返回 NULLPyErr_NoMemory()并逐项释放PyMem_Malloc分配 32 KB 读取缓冲区返回 NULL释放列表对象、关闭文件、PyErr_NoMemory()_Py_DecodeUTF8_surrogateescape解码返回 NULL释放缓冲区与列表对象、PyErr_NoMemory()文件内容超过 32 KBcb MAX_FILE释放列表对象、PyErr_SetString(PyExc_MemoryError, ...)PyUnicode_AsWideCharString/Py_EncodeLocale返回 NULLPyErr_NoMemory()并跳转清理核心要点有三PyErr_NoMemory()是首选信号它等价于PyErr_SetNone(PyExc_MemoryError)是 CPython C API 中表达内存分配失败的标准方式资源清理与错误传播必须成对任何PyMem_Malloc/Py_DECREF持有的资源在失败路径上都需显式释放避免启动阶段内存泄漏或二次崩溃返回 NULL 即表示异常已设置C 函数通过返回 NULL 告知 Python 层错误发生Python 层无需也无法在 C 层继续执行。为什么这个修复对启动阶段如此重要Python 解释器启动初期是一个半初始化状态sys模块尚未完全就绪、异常机制依赖的基础设施正在搭建。此时若 C 代码对内存分配失败处理不当往往表现为空指针解引用导致的段错误SIGSEGV进程直接崩溃未初始化/越界缓冲区写入导致的内存损坏引发难以排查的随机故障错误被静默吞掉后续路径计算基于残缺数据继续执行产生找不到标准库等诡异报错。修复后getpath 将内存不足转化为可被上层捕获的MemoryError使错误以受控方式向上传播。对于在内存受限设备如嵌入式系统、容器内存限额场景上运行的 Python 解释器而言这直接决定了优雅失败还是进程崩溃。相关实现与测试的佐证C 层实现完整的修复逻辑位于 Modules/getpath.c重点关注getpath_readlinesL350-L423与_Py_join_relfileL277-L347两个函数Python 层逻辑Modules/getpath.py 中通过readlines、isfile、joinpath等原语编排启动路径计算虚拟环境与._pth读取分支L353-L361、L475-L481是最典型的调用场景测试基架Lib/test/test_getpath.py 中的测试桩如readlines的模拟实现见 L1026-L1030 与 L1198-L1201说明该模块通过文件系统桩替换来验证各平台路径计算逻辑内存失败路径的回归验证同样依赖此类可注入的桩环境。给 C 扩展开发者的实践启示如果你在编写 Python C 扩展或嵌入式解释器集成代码可以从这次修复中直接复用的模式包括PyObject *buf PyList_New(0); if (!buf) { goto error; /* 分配失败无需额外设置异常 */ } char *tmp (char *)PyMem_Malloc(size); if (!tmp) { Py_DECREF(buf); PyErr_NoMemory(); /* 显式报告 MemoryError */ goto error; } /* ... 使用 tmp 完成工作 ... */ wchar_t *decoded _Py_DecodeUTF8_surrogateescape(tmp, len, decoded_len); PyMem_Free(tmp); if (!decoded) { Py_DECREF(buf); PyErr_NoMemory(); /* 解码失败同样报告 MemoryError */ goto error; } PyMem_RawFree(decoded); return buf; error: return NULL; /* NULL 表示异常已设置调用方继续传播 */要点归纳每次PyMem_Malloc/PyUnicode_FromWideChar/_Py_DecodeUTF8_surrogateescape等调用后必须检查 NULL失败路径上先释放本函数持有的全部资源列表、缓冲区、文件句柄再PyErr_NoMemory()若上层需要区分内存不足与文件过大等不同失败原因可像 32 KB 上限分支那样使用PyErr_SetString(PyExc_MemoryError, ...)附加说明信息最终统一return NULL让异常沿调用链自然传播到 Python 层。总结本次修复以一条简洁的 news 条目记录了getpath.c在设备内存耗尽场景下的健壮性改进通过为启动阶段所有关键内存分配点补充 NULL 检查并调用PyErr_NoMemory()CPython 将原本可能导致崩溃的路径探测流程改造为能够正确抛出MemoryError的受控错误传播。这一改动不仅提升了内存受限设备上的解释器稳定性也为 C 层错误处理提供了可参照的范式——启动阶段的每一处分配都值得一次显式的失败检查。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考