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

macOS 上用 sample <pid> 3 采样,主线程一直停在 REPL 退出时同步 history 文件的路径上,一轮接一轮地重复。对照 Nushell 0.116.1 的源码,链条就清楚了。
原因
- 终端被关闭后,stdout 已经写不进去,写入会返回 EIO。
- Nushell 退出前,如果还有后台任务没结束,会先用
println!提示「再输一次 exit 才真的退出」。 - Rust 的
println!写 stdout 失败会直接 panic。panic 发生时正持有后台任务表的锁,锁随之被标记为 poisoned。 - REPL 每轮迭代外面有一层
catch_unwind,panic 被捕获后会重建 line editor 继续跑;下一轮又走到同一个退出路径,这次直接卡在 poisoned 锁上再次 panic。 - 崩溃、重建、再崩溃,一秒几十次,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 完成。