Nushell 孤儿进程 100% CPU 的排查记录

一个终端早已关闭的 Nushell 进程连续烧了一小时 CPU:退出提示写入死终端触发 panic,又被 REPL 的重试循环无限放大。附一行命令自查同类进程。
Back

现象

今天 macBook 有些温热,打开活动监视器,看到一个 nu 进程长期 100%+ CPU。它没有 tty、父进程是 launchd,说明原终端早已关闭,只剩下这个孤儿进程。奇怪的是它累计 CPU 时间只有约一小时,也就是挂了三天多都相安无事,最近才开始空转。

1792595116194
1792595116194

macOS 上用 sample <pid> 3 采样,主线程一直停在 REPL 退出时同步 history 文件的路径上,一轮接一轮地重复。对照 Nushell 0.116.1 的源码,链条就清楚了。

原因

  1. 终端被关闭后,stdout 已经写不进去,写入会返回 EIO。
  2. Nushell 退出前,如果还有后台任务没结束,会先用 println! 提示「再输一次 exit 才真的退出」。
  3. Rust 的 println! 写 stdout 失败会直接 panic。panic 发生时正持有后台任务表的锁,锁随之被标记为 poisoned。
  4. REPL 每轮迭代外面有一层 catch_unwind,panic 被捕获后会重建 line editor 继续跑;下一轮又走到同一个退出路径,这次直接卡在 poisoned 锁上再次 panic。
  5. 崩溃、重建、再崩溃,一秒几十次,CPU 就满了。每一轮还要重读 history 文件、跑 hooks、克隆状态,所以单轮也不便宜。

panic 时的调用链:

failed printing to stdout: Input/output error (os error 5)    std::io::stdio::_print    nu_engine::exit::cleanup    nu_cli::repl::loop_iteration

整个过程完全静默:报错信息也写往那个已经死掉的终端,唯一症状就是 100% CPU。触发条件是「终端被强制关闭 + 当时还有后台任务」,正常关终端不会遇到;上游报告显示 Linux 同样能复现。

处理

这种进程没有任何可挽救的状态,直接 kill <pid> 即可。

想检查机器上有没有类似情况,在 Nushell 里跑:

ps | where name == 'nu' | where cpu > 50 or ppid == 1
  • cpu 高的是正在空转的;
  • ppid 是 1 的表示原终端已经没了,属于孤儿,迟早会变成前者;
  • 命令行里带脚本路径的(比如 nu foo.nu)可能是自己跑的脚本,别误杀。

上游

这是 Nushell 自身的 bug。同类位置在 #18428 修过(repl.rs、nu-mcp、main.rs),但 nu-engine/src/exit.rs 里的两处 println! 漏掉了。把提示换成不会 panic 的 stdout_write_all_and_flush 即可解决。


本文的排查过程借助 Claude Code 完成。