CPython AsyncIO模块引发进程100%CPU占用挂死问题排查求助
问题描述
我们的Python应用在两台特定机器上运行10-20分钟后出现挂死现象,Htop显示CPU占用率达100%。使用Pystack获取进程栈跟踪信息后发现,Python层面仅显示普通字典查找操作(每次挂死时位置不同),但最终调用显示进程卡在CPython 3.12.3版本_asynciomodule.c文件的module_traverse函数的while循环中。相关代码片段如下:
module_traverse(PyObject *mod, visitproc visit, void *arg) { asyncio_state *state = get_asyncio_state(mod); Py_VISIT(state->FutureIterType); Py_VISIT(state->TaskStepMethWrapper_Type); Py_VISIT(state->FutureType); Py_VISIT(state->TaskType); Py_VISIT(state->asyncio_mod); Py_VISIT(state->traceback_extract_stack); Py_VISIT(state->asyncio_future_repr_func); Py_VISIT(state->asyncio_get_event_loop_policy); Py_VISIT(state->asyncio_iscoroutine_func); Py_VISIT(state->asyncio_task_get_stack_func); Py_VISIT(state->asyncio_task_print_stack_func); Py_VISIT(state->asyncio_task_repr_func); Py_VISIT(state->asyncio_InvalidStateError); Py_VISIT(state->asyncio_CancelledError); Py_VISIT(state->scheduled_tasks); Py_VISIT(state->eager_tasks); Py_VISIT(state->current_tasks); Py_VISIT(state->iscoroutine_typecache); Py_VISIT(state->context_kwname); // Visit freelist. PyObject *next = (PyObject*) state->fi_freelist; while (next != NULL) { // stuck inside this loop PyObject *current = next; Py_VISIT(current); next = (PyObject*) ((futureiterobject*) current)->future; } return 0; }
推测这段代码与垃圾回收相关,想了解从中能得到什么排查线索,以及下一步该从何处着手。
排查线索与下一步方案
核心排查线索
- 循环死锁根源:
module_traverse是CPython垃圾回收(GC)阶段用于遍历asyncio模块内PyObject引用的函数,卡在fi_freelist的while循环,说明futureiter对象的链表出现了循环引用——某个futureiterobject的future指针指向了链表中前面的节点,导致遍历永远无法终止,CPU被无限循环占满。 - 触发场景特征:仅在两台特定机器出现问题,说明可能和这两台机器的环境差异有关(比如系统内核版本、libc版本、asyncio使用模式差异,或是进程运行时的资源限制);运行10-20分钟才触发,说明是累积性问题,随着
fi_freelist链表长度增加,循环引用的影响才显现。 - Python层表象的误导性:Python层显示普通字典查找,是因为GC触发时机不确定——字典操作可能刚好触发了GC,而实际卡死点在GC的底层遍历逻辑,Python栈只是触发GC的上下文。
下一步排查方向
- 验证循环引用假设
- 挂死时用gdb附加进程,打印
state->fi_freelist链表的节点内容,检查是否存在future指针指向链表中已遍历过的节点,确认循环链的具体结构。 - 可编写简单的gdb脚本自动化遍历链表,定位循环节点的内存地址。
- 挂死时用gdb附加进程,打印
- 排查特定机器的环境差异
- 对比两台问题机器与正常机器的:CPython编译参数、系统内核版本、glibc版本、asyncio版本(如果是自定义编译的Python)、进程的资源限制(如
ulimit参数)。 - 检查这两台机器是否运行了其他可能干扰内存布局的进程,或是使用了特殊的内存管理工具(如tcmalloc、jemalloc)。
- 对比两台问题机器与正常机器的:CPython编译参数、系统内核版本、glibc版本、asyncio版本(如果是自定义编译的Python)、进程的资源限制(如
- 追踪futureiter对象的生命周期逻辑
- 查看CPython源码中
futureiterobject的创建(futureiter_new)、释放(futureiter_dealloc)时的链表操作逻辑,是否存在并发场景下的链表操作不安全(比如多线程/多协程修改fi_freelist时未加锁)。 - 检查应用中是否有大量创建
Future迭代器的场景(比如频繁调用asyncio.as_completed、手动遍历Future对象),这类操作会持续向fi_freelist中添加节点。
- 查看CPython源码中
- 临时规避与验证
- 尝试升级CPython到更高版本(如3.12.4+或3.13预览版),查看是否已有官方修复该循环引用的补丁。
- 临时修改CPython源码注释掉遍历
fi_freelist的逻辑并重新编译,观察问题是否消失——如果消失,可确认问题根源就在该链表的循环引用。
- 应用层代码检查
- 排查应用中是否存在自定义Future子类或手动操作Future迭代器的代码,这类代码可能破坏了asyncio内部的链表管理逻辑。
- 检查是否有未正确处理的协程取消、Future状态异常的场景,这类异常可能导致futureiter对象的
future指针被错误赋值。
内容的提问来源于stack exchange,提问作者Sophon Aniketos
相关产品推荐
相关产品推荐

