// src/node_http2.cc
int Http2Session::OnStreamClose(nghttp2_session* handle,
int32_t id,
uint32_t code,
void* user_data) {
Http2Session* session = static_cast<Http2Session*>(user_data);
Environment* env = session->env();
Isolate* isolate = env->isolate();
HandleScope scope(isolate);
Local<Context> context = env->context();
Context::Scope context_scope(context);
Debug(session, "stream %d closed with code: %d", id, code);
BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
// Intentionally ignore the callback if the stream does not exist or has
// already been destroyed
if (!stream || stream->is_destroyed())
return 0;
stream->Close(code);
// It is possible for the stream close to occur before the stream is
// ever passed on to the javascript side. If that happens, the callback
// will return false.
Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
MaybeLocal<Value> answer =
stream->MakeCallback(env->http2session_on_stream_close_function(),
1, &arg);
if (answer.IsEmpty() || answer.ToLocalChecked()->IsFalse()) {
// Skip to destroy
stream->Destroy();
}
return 0;
}
问题背景
y 同学反映电脑换成 M2 后,使用 nopack 开发时经常会出现 CPU 占用 100% 的情况,已经严重影响到了开发效率
相比于 webpack 这样的 bundler 📦 打包后产物只有少量的文件,Vite 与 nopack 这样的 bundless ⚡️ 工具不会在开发模式下打包应用的源代码。 这两者是基于浏览器 native ES Modules,这样的模式让热更新无比的快速,因为只需编译转换一个文件,不用像 bundler 那样从头开始打包
所以 bundless 工具开发模式下往往会出现大量的小文件请求,对于性能较差的电脑 Google Chrome 进程出现短暂的 CPU 占用 100% 的情况还是比较常见
该同学使用的是最强 M2,问题表现是 nopack 进程偶现 CPU 占用 100% 且不会下降,这属实让人一下难以接受
问题排查
一开始只能要 y 同学通过 node --cpu-prof 选项去启动 nopack,当程序退出时希望够从自动生成的 .cpuprofile 文件中找到突破口
遗憾的是如上图 .cpuprofile 文件中未能发现 🔍 有占用 CPU 过高的函数,没有发现较大价值的线索。另一个问题是 10次退出程序中大概只有 1次会生成 .cpuprofile 文件 🥲,分析文件的生成也不稳定 ...
下一步的排查中我在 nopack 代码中加入了如下的心跳检测代码,确认进程是不是真瘫痪了
结果是当 CPU 占用到了 100% 时,setInterval 里面的 console 也停止了,此时基本确认了进程可能陷入了某种致命错误或者死循环中,这大概率是程序的 bug 了 🐛
最后只能寄希望于调试利器 lldb 了,来看看进程卡死后,最后运行的代码是什么
通过 lldb 线程的堆栈回溯,发现了最后堆栈的停留在了
node::http2::Http2Session::OnStreamClose
函数调用,这是一个很可疑的点因为对比一个正常运行的 nopack 进程的堆栈回溯时,已经运行结束的
node::http2::Http2Session::OnStreamClose
等函数就没有出现在如下的堆栈中前面说了 bundless 工具开发模式下往往会出现大量的小文件请求,所以 nopack 默认是 http2 协议启动,相比于 http1 协议能够大幅提升并发请求
而这里显示停留在了 Node.js http2 的 OnStreamClose 方法,如果尝试使用 http1 协议来启动没有问题就说明了 CPU 占用 100% 极有可能是 Node.js http2 在 M2 中的一个 bug
问题小结
使用 http1 协议后暂未能再复现 CPU 占用 100% 的问题,至于是不是 Node.js http2 的 bug,还需打个 Node.js debug 包来进行排查与确认,只能后续需求不忙再跟进了