You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的上下文。

下一步排查方向

  1. 验证循环引用假设
    • 挂死时用gdb附加进程,打印state->fi_freelist链表的节点内容,检查是否存在future指针指向链表中已遍历过的节点,确认循环链的具体结构。
    • 可编写简单的gdb脚本自动化遍历链表,定位循环节点的内存地址。
  2. 排查特定机器的环境差异
    • 对比两台问题机器与正常机器的:CPython编译参数、系统内核版本、glibc版本、asyncio版本(如果是自定义编译的Python)、进程的资源限制(如ulimit参数)。
    • 检查这两台机器是否运行了其他可能干扰内存布局的进程,或是使用了特殊的内存管理工具(如tcmalloc、jemalloc)。
  3. 追踪futureiter对象的生命周期逻辑
    • 查看CPython源码中futureiterobject的创建(futureiter_new)、释放(futureiter_dealloc)时的链表操作逻辑,是否存在并发场景下的链表操作不安全(比如多线程/多协程修改fi_freelist时未加锁)。
    • 检查应用中是否有大量创建Future迭代器的场景(比如频繁调用asyncio.as_completed、手动遍历Future对象),这类操作会持续向fi_freelist中添加节点。
  4. 临时规避与验证
    • 尝试升级CPython到更高版本(如3.12.4+或3.13预览版),查看是否已有官方修复该循环引用的补丁。
    • 临时修改CPython源码注释掉遍历fi_freelist的逻辑并重新编译,观察问题是否消失——如果消失,可确认问题根源就在该链表的循环引用。
  5. 应用层代码检查
    • 排查应用中是否存在自定义Future子类或手动操作Future迭代器的代码,这类代码可能破坏了asyncio内部的链表管理逻辑。
    • 检查是否有未正确处理的协程取消、Future状态异常的场景,这类异常可能导致futureiter对象的future指针被错误赋值。

内容的提问来源于stack exchange,提问作者Sophon Aniketos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 12:23:33