Dav*_*ard 3 node.js express cls-hooked
以下代码会增加内存使用量直至崩溃:
const httpContext = require('express-http-context');
async function t2() {
}
async function t1() {
for (let i = 0; i < 100000000; i++) {
httpContext.ns.run(t2);
}
}
t1();
Run Code Online (Sandbox Code Playgroud)
运行它:node --inspect --max-old-space-size=300 ns
问题:命名空间 _contexts 映射永远不会被清理。
cls-hooked/context.js 中有一个函数 destroy(id) 但它从未被调用。
我还尝试了 ns.bind、ns.runPromise (它执行 ns.exit())和 ns.bind
运行结束后如何删除上下文?
代码:
const httpContext = require('express-http-context');
function t2() {
}
async function t1() {
for (let i = 0; i < 100000000; i++) {
httpContext.ns.run(t2);
}
}
t1();
Run Code Online (Sandbox Code Playgroud)
作品。
代码:
const httpContext = require('express-http-context');
async function t3() {
}
function t2() {
t3();
}
async function t1() {
for (let i = 0; i < 100000000; i++) {
httpContext.ns.run(t2);
}
}
t1();
Run Code Online (Sandbox Code Playgroud)
又出现内存泄漏了。
cls-hook async_hook 方法 init() 将上下文添加到 _contexts 映射中。cls-hook async_hook 方法 destroy() 从 _contexts 映射中删除上下文。
问题是 destroy 永远不会被调用。
这是 cls-hooks 中的错误还是与当前 async_hooks 不兼容?
正如OP所指出的,这种用法肯定是不正确的。
OP 应该只执行ns.run()
一次,并且其中的所有内容都run
将具有相同的上下文。
看一下这个正确用法的例子:
var createNamespace = require('cls-hooked').createNamespace;
var writer = createNamespace('writer');
writer.run(function () {
writer.set('value', 0);
requestHandler();
});
function requestHandler() {
writer.run(function(outer) {
// writer.get('value') returns 0
// outer.value is 0
writer.set('value', 1);
// writer.get('value') returns 1
// outer.value is 1
process.nextTick(function() {
// writer.get('value') returns 1
// outer.value is 1
writer.run(function(inner) {
// writer.get('value') returns 1
// outer.value is 1
// inner.value is 1
writer.set('value', 2);
// writer.get('value') returns 2
// outer.value is 1
// inner.value is 2
});
});
});
setTimeout(function() {
// runs with the default context, because nested contexts have ended
console.log(writer.get('value')); // prints 0
}, 1000);
}
Run Code Online (Sandbox Code Playgroud)
此外,内部的实现cls-hooked
确实表明上下文是通过异步钩子回调销毁的destroy(asyncId)
destroy(asyncID)
在对应的资源被销毁后调用asyncId
。它也可以从嵌入器 API emitDestroy() 异步调用。有些resources
依赖于垃圾收集来进行清理,因此如果对传递给的资源对象进行引用init
该引用可能destroy
永远不会被调用,从而导致应用程序中的内存泄漏。如果资源不依赖于垃圾回收,那么这将不是问题。
https://github.com/Jeff-Lewis/cls-hooked/blob/0ff594bf6b2edd6fb046b10b67363c3213e4726c/context.js#L416-L425
这是我的存储库,用于通过使用大量请求轰炸服务器来比较和测试运行内存使用情况autocannon
这是我的存储库,用于通过使用https://github.com/Darkripper214/AsyncMemoryTest
根据测试,利用率的增加可以忽略不计heap
(正如预期的那样,因为我们正在处理 HTTP 请求)。
cls-hooked
该存储库是一个微型测试,用于查看在使用和async-hook
传递上下文时如何利用内存Node.js
。
npm run start
对于 CLS-hook 服务器或 npm run async
Async-hook 服务器
转到 Chrome 并粘贴chrome://inspect
点击inspect
进入服务器的开发工具
转到memory
选项卡,您可以拍摄快照并检查heap
请求轰炸服务器之前、期间和之后的情况
node benchmark.js
开始用请求轰炸服务器。这是由 提供支持的autocannon
,您可能想要增加connections
或duration
查看差异。
统计数据 | 1% | 2.5% | 50% | 97.5% | 平均 | 标准差 | 最大限度 |
---|---|---|---|---|---|---|---|
请求/秒 | 第839章 | 第839章 | 第871章 | 第897章 | 870.74 | 14.23 | 第839章 |
字节/秒 | 237kB | 237kB | 246kB | 253kB | 246kB | 4.01kB | 237kB |
每秒采样一次的请求/字节计数(请注意,这是在附加调试器的情况下运行的,每秒的性能会受到影响)
15.05 秒内 13k 请求,读取 3.68 MB
统计数据 | 1% | 2.5% | 50% | 97.5% | 平均 | 标准差 | 最大限度 |
---|---|---|---|---|---|---|---|
请求/秒 | 300 | 300 | 第347章 | 400 | 346.4 | 31.35 | 300 |
字节/秒 | 84.6kB | 84.6kB | 97.9kB | 113kB | 97.7kB | 8.84kB | 84.6kB |
每秒采样一次的请求/字节计数(请注意,这是在附加调试器的情况下运行的,并且有大量debug()
消息来显示它如何store
是如何被破坏的,每秒的性能将受到影响)
15.15 秒内 5k 请求,读取 1.47 MB
编辑1
OP 抱怨_context
每次设置的长度namespace.run()
执行 a 时设置的长度。正如前面强调的,OP 测试的方式不正确,因为它是在循环上运行的。
OP 抱怨的情况仅在namespace.run()
执行某些回调或包含async function
.
async function t3() {} // This async function will cause _context length to not be cleared
function t2() {
t3();
}
function t1() {
for (let i = 0; i < 500; i++) {
session.run(t2);
}
}
t1();
Run Code Online (Sandbox Code Playgroud)
那么为什么_context没有被清除呢?这是因为async function t3
无法在 Node.js 中运行,event loop
因为它synchronous for loop
正在连续运行,因此几乎无限地将项目附加到_context
.
因此,为了证明这是由于这种行为造成的,我更新了存储库以包含一个cls-gc.js
可以使用 运行的文件npm run gc
,该文件在其间显式运行垃圾收集,并且垃圾收集不会影响_context
。
_context
执行期间的长度会很长t1()
,t2()
因为两者都是同步的。然而, 的长度大约_context
是在setTimeout
调用回调之后。请使用调试器来检查这一点。
的长度_context
可在session
// process.env.DEBUG_CLS_HOOKED = true;
('use strict');
let createNamespace = require('cls-hooked').createNamespace;
let session = createNamespace('benchmark');
async function t3() {}
function t2() {
t3();
}
function t1() {
for (let i = 0; i < 500; i++) {
session.run(t2);
try {
if (global.gc) {
global.gc();
console.log('garbage collection ran');
}
} catch (e) {
console.log('`node --expose-gc index.js`');
process.exit();
}
}
}
t1();
function t5() {
for (let i = 0; i < 1000; i++) {
// Check the _context here, should have length of at least 500
session.run(t2);
try {
if (global.gc) {
global.gc();
console.log('garbage collection ran');
}
} catch (e) {
console.log('`node --expose-gc index.js`');
process.exit();
}
}
}
t5();
setTimeout(() => {
console.log('here');
// Check the _context here, length should be 0
session.run(t2);
}, 3000);
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
5681 次 |
最近记录: |