是异步任务互锁型死锁;表现为断点命中后CPU先飙至95%+再骤降至0%、请求无响应、console.log不输出、process.uptime()停止增长,且调试器因事件循环停滞而断连,断点不亮、Debug Console灰掉。

断点命中但 CPU 骤降为 0%,是不是异步死锁?
不是所有卡住都是死锁,但如果你在 VSCode 里 F5 启动后断点停住、几秒内 CPU 从 95%+ 直接掉到 0%,且后续请求全无响应、console.log 不输出、process.uptime() 停止增长——这极大概率是事件循环彻底停滞,属于异步任务互锁型死锁,而非同步阻塞(如 fs.readFileSync)或 IO 等待。
CommonJS 循环引用也会卡,但它表现为 TypeError: xxx is not a function 或读取 undefined 属性,不会触发 CPU 飙升再归零;而异步死锁会让 V8 的 inspector 心跳超时,调试器自动断连,断点不亮、Debug Console 灰掉。
- 验证方法:卡死时按
Ctrl+C,看终端末尾是否残留session_id not found、Unknown state session或output lock - 另开终端执行
codex session list,若返回多个状态为Alive但实际已超时的会话 ID,基本可确认是异步互锁 - 别用
nodemon或PM2调试——它们会干扰--inspect端口暴露,直接运行node --inspect-brk index.js
launch.json 配置错误导致调试器根本连不上
VSCode 调试器连不上,常被误判为“网络问题”或“端口被占”,其实根本原因是 Node 进程压根没进入可调试状态——它卡在启动阶段,连 --inspect 服务都没起来。
-
"program"必须指向真实存在的 JS 入口文件,例如"${workspaceFolder}/dist/index.js";写成"src/index.js"或"./src/index.js"会失败 - 不要依赖
attach模式,除非你在 npm script 里显式加了--inspect-brk;npm start默认不暴露调试端口 -
"request"必须为"launch",不能是"attach"(除非你手动管理进程和端口) - Node 版本需 ≥ 14,且避开预发布版(如
v24.16.0),否则async_hooks可能失效,调试器完全失联
如何让 VSCode 暴露事件循环底层状态
默认 launch.json 不提供事件循环可观测性。要定位死锁根源,必须主动启用 Node 内部追踪,并让调试器捕获调度毛刺。
- 在
launch.json的"runtimeArgs"中加入:--inspect-brk=9229和--trace-event-categories v8,disabled-by-default-node(后者是关键,开启loop、timer、idle阶段标记) - 通过
"env"注入:NODE_OPTIONS="--trace-events-enabled --trace-event-file=./tracing.json",生成可导入 Chrome DevTools 分析的火焰图 - 启动后,在 Debug Console 手动执行:
require('perf_hooks').performance.eventLoopUtilization(),若active值长期接近 0,说明事件循环已无活跃任务 - 入口文件顶部加轻量探测:
const start = performance.now(); process.nextTick(() => { const delta = performance.now() - start; if (delta > 50) console.warn('Event loop delay:', delta, 'ms'); });
用 CodeGeeX 快速反推锁持有关系
人工扫 Promise 链效率极低,真正有效的办法是让工具解析日志中的锁上下文。CodeGeeX 插件能直接从终端输出中识别互锁模式。
- 卡死后按
Ctrl+C,全选从Error或session_id开始的日志片段 - 右键终端 → 选择 “使用 CodeGeeX 解释”,它会自动输出类似:“任务 A (
session write) 等待output lock释放,任务 B (response flush) 正持有该 lock 并等待session状态更新” - 修复指令示例:
codex fix --file %LOCALAPPDATA%\Codex\config.toml --context "enable auto_cleanup_on_exit and set max_concurrent_sessions=8" - 应用补丁后,再执行
codex session list,若输出为空行,说明残留会话已被清除,新配置生效
真正难的不是发现死锁,而是区分它是异步互锁、CommonJS 循环引用,还是调试器本身因环境变量未继承(比如 PATH 缺失)而无法加载 runtime——这三个层面的问题现象高度相似,但修复路径完全不同。


















