Memory leak in cls-hooked or wrong use? - node.js

The following code increases memory usage until crash:
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 it with: node --inspect --max-old-space-size=300 ns
The problem: The namespace _contexts map is never cleaned up.
There is a function destroy(id) inside cls-hooked/context.js but that it is never called.
I tried also ns.bind, ns.runPromise (which does a ns.exit()) and ns.bind
How can I delete the contexts after a run is finished?
The code:
const httpContext = require('express-http-context');
function t2() {
}
async function t1() {
for (let i = 0; i < 100000000; i++) {
httpContext.ns.run(t2);
}
}
t1();
works.
The code:
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();
has the memory leak again.
The cls-hooked async_hook method init() adds the context to the _contexts map.
The cls-hooked async_hook method destroy() deletes the context from the _contexts map.
The problem is that destroy is never called.
Is this a bug in cls-hooked or an incompatibility to the current async_hooks?

As pointed out to OP, the usage is definitely incorrect.
OP should only execute ns.run() once, and everything within that run will be of the same context.
Look at this example of proper usage:
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);
}
Furthermore, the implementation inside cls-hooked do show that context is destroyed via async hook callback destroy(asyncId)
destroy(asyncID) is called after the resource corresponding to asyncId is destroyed. It is also called asynchronously from the embedder API emitDestroy(). Some resources depend on garbage collection for cleanup, so if a reference is made to the resource object passed to init it is possible that destroy will never be called, causing a memory leak in the application. If the resource does not depend on garbage collection, then this will not be an issue.
https://github.com/Jeff-Lewis/cls-hooked/blob/0ff594bf6b2edd6fb046b10b67363c3213e4726c/context.js#L416-L425
Here is my repo for comparison and test-run of memory usage by bombarding the server with tonnes of requests using autocannon
https://github.com/Darkripper214/AsyncMemoryTest
Based on the test, there is a negligible increase in the utilization of heap (As expected, as we're processing HTTP requests).
Memory Utilization of CLS-Hooked & Async-Hook
Purpose
The repository is a miniature test to see how is memory utilized when using cls-hooked and async-hook to pass context within Node.js.
Usage
npm run start for CLS-hooked server or npm run async for Async-hook server
Go to Chrome and paste chrome://inspect
Click inspect to access to Dev Tools of the server
Go to memory tab, you may take snapshot and inspect the heap before, during and after bombarding the server with requests
node benchmark.js to start bombarding server with requests. This is powered by autocannon, you may want to increase connections or duration to see the difference.
Results
CLS-hooked
Stat
1%
2.5%
50%
97.5%
Avg
Stdev
Max
Req/Sec
839
839
871
897
870.74
14.23
839
Bytes/Sec
237kB
237kB
246kB
253kB
246kB
4.01kB
237kB
Req/Bytes counts sampled once per second (Note that this is ran with debugger attached, performance per second would be impacted)
13k requests in 15.05s, 3.68 MB read
Async-Hook
Stat
1%
2.5%
50%
97.5%
Avg
Stdev
Max
Req/Sec
300
300
347
400
346.4
31.35
300
Bytes/Sec
84.6kB
84.6kB
97.9kB
113kB
97.7kB
8.84kB
84.6kB
Req/Bytes counts sampled once per second (Note that this is ran with debugger attached & plenty of debug() messages to show how it store is destroyed, performance per second would be impacted)
5k requests in 15.15s, 1.47 MB read
Edit 1
OP is complaining on the length of _context which is set every time a namespace.run() is executed. As highlighted earlier, the way OP is testing is not correct, as it is running on a loop.
The scenario that OP is complaining will only occurs when namespace.run() execute some callback that is or contain an 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();
So why _context is not cleared? This is because async function t3 won't be able to be run in the node.js event loop as the synchronous for loop is continuously running, hence the near-infinite appending of item into _context.
So to prove that this is due to this behavior, I've updated the repo to include a file cls-gc.js that can be run using npm run gc, which explicitly run garbage collection in between, and garbage collection won't affect the length of _context.
The length of _context will be long during execution of t1() and t2() as both is synchronous. However, the length of _context will be about right after the setTimeout callback is called. Please use debugger to check for this.
Length of _context will be available at the 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);

Related

Am I performance testing correctly? Library "Memored" storage vs. normal RAM storage in Node.js

I am trying to compare the READ performance of a library called Memored to regular old RAM variables in Node.js.
I expected that the data stored with Memored to be at least slightly slower than RAM storage in terms of reading data, but the results show the opposite (read below for my outputs).
I am running this in the terminal of Visual Studio Code on Windows 10. It’s all being done in Typescript, which gets compiled down to JavaScript later and then run with the "node" command.
This is my RAM test:
var normalRAM = {
firstname: 'qwe',
lastname: 'fsa'
}
var s = process.hrtime(); //start timer
console.log(normalRAM); // read from ram
var e = process.hrtime(s) //stop timer
console.log("end0", e[0]); //results in seconds
console.log("end1", e[1]); //results in nanoseconds
This is my Memored test:
// Clustering needed to show Memored in action
if (cluster.isMaster)
{
// Fork workers.
for (let i = 0; i < 1; i++)
{
cluster.fork();
}
}
else
{
var han = {
firstname: 'Han',
lastname: 'Solo'
}
// Store and read
memored.store('character1', han, function ()
{
console.log('Value stored!');
var hrstart = process.hrtime(); // start timer
memored.read('character1', function (err: any, value: any)
{
var hrend = process.hrtime(hrstart) // stop timer
console.log('Read value:', value);
console.log("hrend0", hrend[0]); //results in seconds
console.log("hrend1", hrend[1]); //results in nanoseconds
});
});
}
The results:
The RAM read speeds are around 6500000 nanoseconds.
The Memored read speeds are around 1000000 nanoseconds
Am I testing the speeds incorrectly here? What are the flaws in my methodology? Perhaps my initial assumption is wrong?
I switched the following two lines:
var hrend = process.hrtime(hrstart) // stop timer
console.log('Read value:', value);
To this:
console.log('Read value:', value);
var hrend = process.hrtime(hrstart) // stop timer
Which makes more sense in a real scenario since I would need to read it from RAM like that anyway after the data is returned. The answer to my question is probably "your Memored test is performing faster, because it’s only testing when the data comes back for my callback to use, and not when I actually read it from the 'value' variable".

Node.js Calling functions as quickly as possible without going over some limit

I have multiple functions that call different api endpoints, and I need to call them as quickly as possible without going over some limit (20 calls per second for example). My current solution is to have a delay and call the function once every 50 milliseconds for the example I gave, but I would like to call them as quickly as possible and not just space out the calls equally with the rate limit.
function-rate-limit solved a similar problem for me. function-rate-limit spreads out calls to your function over time, without dropping calls to your function. It still allows instantaneous calls to you function until the rate limit is reached, so it can behave with no latency introduced under normal circumstances.
Example from function-rate-limit docs:
var rateLimit = require('function-rate-limit');
// limit to 2 executions per 1000ms
var start = Date.now()
var fn = rateLimit(2, 1000, function (x) {
console.log('%s ms - %s', Date.now() - start, x);
});
for (var y = 0; y < 10; y++) {
fn(y);
}
results in:
10 ms - 0
11 ms - 1
1004 ms - 2
1012 ms - 3
2008 ms - 4
2013 ms - 5
3010 ms - 6
3014 ms - 7
4017 ms - 8
4017 ms - 9
You can try using queue from async. Be careful when doing this, it essentially behaves like a while(true) in other languages:
const async = require('async');
const concurrent = 10; // At most 10 concurrent ops;
const tasks = Array(concurrent).fill().map((e, i) => i);
let pushBack; // let's create a ref to a lambda function
const myAsyncFunction = (task) => {
// TODO: Swap with the actual implementation
return Promise.resolve(task);
};
const q = async.queue((task, cb) => {
myAsyncFunction(task)
.then((result) => {
pushBack(task);
cb(null, result);
})
.catch((err) => cb(err, null));
}, tasks.length);
pushBack = (task) => q.push(task);
q.push(tasks);
What's happening here? We are saying "hey run X tasks in parallel" and after each task gets completed, we put it back in the queue which is the equivalent of saying "run X tasks in parallel forever"

Node.js setTimeout() behaviour

I want a piece of code to repeat 100 times with 1 sec of delay in between. This is my code:
for(var i = 0; i < 100; i++){
setTimeout(function(){
//do stuff
},1000);
}
While this seems correct to me it is not. Instead of running "do stuff" 100 times and waiting 1 sec in between what it does is wait 1 sec and then run "do stuff" 100 times with no delay.
Anybody has any idea about this?
You can accomplish it by using setInterval().
It calls function of our choice as long as clearTimeout is called to a variable timer which stores it.
See example below with comments: (and remember to open your developer console (in chrome right click -> inspect element -> console) to view console.log).
// Total count we have called doStuff()
var count = 0;
/**
* Method for calling doStuff() 100 times
*
*/
var timer = setInterval(function() {
// If count increased by one is smaller than 100, keep running and return
if(count++ < 100) {
return doStuff();
}
// mission complete, clear timeout
clearTimeout(timer);
}, 1000); // One second in milliseconds
/**
* Method for doing stuff
*
*/
function doStuff() {
console.log("doing stuff");
}
Here is also: jsfiddle example
As a bonus: Your original method won't work because you are basically assigning 100 setTimeout calls as fast as possible. So instead of them running with one second gaps. They will run as fast as the for loop is placing them to queue, starting after 1000 milliseconds of current time.
For instance, following code shows timestamps when your approach is used:
for(var i = 0; i < 100; i++){
setTimeout(function(){
// Current time in milliseconds
console.log(new Date().getTime());
},1000);
}
It will output something like (milliseconds):
1404911593267 (14 times called with this timestamp...)
1404911593268 (10 times called with this timestamp...)
1404911593269 (12 times called with this timestamp...)
1404911593270 (15 times called with this timestamp...)
1404911593271 (12 times called with this timestamp...)
You can see the behaviour also in: js fiddle
You need to use callback, node.js is asynchronous:
function call_1000times(callback) {
var i = 0,
function do_stuff() {
//do stuff
if (i < 1000) {
i = i + 1;
do_stuff();
} else {
callback(list);
}
}
do_stuff();
}
Or, more cleaner:
setInterval(function () {
//do stuff
}, 1000);
Now that you appreciate that the for loop is iterating in a matter of milliseconds, another way to do it would be to simply adjust the setTimeout delay according to the count.
for(var i = 0; i < 100; i++){
setTimeout(function(){
//do stuff
}, i * 1000);
}
For many use-cases, this could be seen as bad. But in particular circumstances where you know that you definitely want to run code x number of times after y number of seconds, it could be useful.
It's also worth noting there are some that believe using setInterval is bad practise.
I prefer the recursive function. Call the function initially with the value of counter = 0, and then within the function check to see that counter is less than 100. If so, do your stuff, then call setTimeout with another call to doStuff but with a value of counter + 1. The function will run exactly 100 times, once per second, then quit :
const doStuff = counter => {
if (counter < 100) {
// do some stuff
setTimeout(()=>doStuff(counter + 1), 1000)
}
return;
}
doStuff(0)

Best way to execute parallel processing in Node.js

I'm trying to write a small node application that will search through and parse a large number of files on the file system.
In order to speed up the search, we are attempting to use some sort of map reduce. The plan would be the following simplified scenario:
Web request comes in with a search query
3 processes are started that each get assigned 1000 (different) files
once a process completes, it would 'return' it's results back to the main thread
once all processes complete, the main thread would continue by returning the combined result as a JSON result
The questions I have with this are:
Is this doable in Node?
What is the recommended way of doing it?
I've been fiddling, but come no further then following example using Process:
initiator:
function Worker() {
return child_process.fork("myProcess.js");
}
for(var i = 0; i < require('os').cpus().length; i++){
var process = new Worker();
process.send(workItems.slice(i * itemsPerProcess, (i+1) * itemsPerProcess));
}
myProcess.js
process.on('message', function(msg) {
var valuesToReturn = [];
// Do file reading here
//How would I return valuesToReturn?
process.exit(0);
}
Few sidenotes:
I'm aware the number of processes should be dependent of the number of CPU's on the server
I'm also aware of speed restrictions in a file system. Consider it a proof of concept before we move this to a database or Lucene instance :-)
Should be doable. As a simple example:
// parent.js
var child_process = require('child_process');
var numchild = require('os').cpus().length;
var done = 0;
for (var i = 0; i < numchild; i++){
var child = child_process.fork('./child');
child.send((i + 1) * 1000);
child.on('message', function(message) {
console.log('[parent] received message from child:', message);
done++;
if (done === numchild) {
console.log('[parent] received all results');
...
}
});
}
// child.js
process.on('message', function(message) {
console.log('[child] received message from server:', message);
setTimeout(function() {
process.send({
child : process.pid,
result : message + 1
});
process.disconnect();
}, (0.5 + Math.random()) * 5000);
});
So the parent process spawns an X number of child processes and passes them a message. It also installs an event handler to listen for any messages sent back from the child (with the result, for instance).
The child process waits for messages from the parent, and starts processing (in this case, it just starts a timer with a random timeout to simulate some work being done). Once it's done, it sends the result back to the parent process and uses process.disconnect() to disconnect itself from the parent (basically stopping the child process).
The parent process keeps track of the number of child processes started, and the number of them that have sent back a result. When those numbers are equal, the parent received all results from the child processes so it can combine all results and return the JSON result.
For a distributed problem like this, I've used zmq and it has worked really well. I'll give you a similar problem that I ran into, and attempted to solve via processes (but failed.) and then turned towards zmq.
Using bcrypt, or an expensive hashing algorith, is wise, but it blocks the node process for around 0.5 seconds. We had to offload this to a different server, and as a quick fix, I used essentially exactly what you did. Run a child process and send messages to it and get it to
respond. The only issue we found is for whatever reason our child process would pin an entire core when it was doing absolutely no work.(I still haven't figured out why this happened, we ran a trace and it appeared that epoll was failing on stdout/stdin streams. It would also only happen on our Linux boxes and would work fine on OSX.)
edit:
The pinning of the core was fixed in https://github.com/joyent/libuv/commit/12210fe and was related to https://github.com/joyent/node/issues/5504, so if you run into the issue and you're using centos + kernel v2.6.32: update node, or update your kernel!
Regardless of the issues I had with child_process.fork(), here's a nifty pattern I always use
client:
var child_process = require('child_process');
function FileParser() {
this.__callbackById = [];
this.__callbackIdIncrement = 0;
this.__process = child_process.fork('./child');
this.__process.on('message', this.handleMessage.bind(this));
}
FileParser.prototype.handleMessage = function handleMessage(message) {
var error = message.error;
var result = message.result;
var callbackId = message.callbackId;
var callback = this.__callbackById[callbackId];
if (! callback) {
return;
}
callback(error, result);
delete this.__callbackById[callbackId];
};
FileParser.prototype.parse = function parse(data, callback) {
this.__callbackIdIncrement = (this.__callbackIdIncrement + 1) % 10000000;
this.__callbackById[this.__callbackIdIncrement] = callback;
this.__process.send({
data: data, // optionally you could pass in the path of the file, and open it in the child process.
callbackId: this.__callbackIdIncrement
});
};
module.exports = FileParser;
child process:
process.on('message', function(message) {
var callbackId = message.callbackId;
var data = message.data;
function respond(error, response) {
process.send({
callbackId: callbackId,
error: error,
result: response
});
}
// parse data..
respond(undefined, "computed data");
});
We also need a pattern to synchronize the different processes, when each process finishes its task, it will respond to us, and we'll increment a count for each process that finishes, and then call the callback of the Semaphore when we've hit the count we want.
function Semaphore(wait, callback) {
this.callback = callback;
this.wait = wait;
this.counted = 0;
}
Semaphore.prototype.signal = function signal() {
this.counted++;
if (this.counted >= this.wait) {
this.callback();
}
}
module.exports = Semaphore;
here's a use case that ties all the above patterns together:
var FileParser = require('./FileParser');
var Semaphore = require('./Semaphore');
var arrFileParsers = [];
for(var i = 0; i < require('os').cpus().length; i++){
var fileParser = new FileParser();
arrFileParsers.push(fileParser);
}
function getFiles() {
return ["file", "file"];
}
var arrResults = [];
function onAllFilesParsed() {
console.log('all results completed', JSON.stringify(arrResults));
}
var lock = new Semaphore(arrFileParsers.length, onAllFilesParsed);
arrFileParsers.forEach(function(fileParser) {
var arrFiles = getFiles(); // you need to decide how to split the files into 1k chunks
fileParser.parse(arrFiles, function (error, result) {
arrResults.push(result);
lock.signal();
});
});
Eventually I used http://zguide.zeromq.org/page:all#The-Load-Balancing-Pattern, where the client was using the nodejs zmq client, and the workers/broker were written in C. This allowed us to scale this across multiple machines, instead of just a local machine with sub processes.

how to prevent memory leak in javascript

i stuck into memory leak in js problems.
Javascript:
var index = 0;
function leak() {
console.log(index);
index++;
setTimeout(leak, 0);
}
leak();
here is my test codes, and i use instruments.app to detect memory use of it,
and the memory is going up very fast.
i am doubt that there seems no variables occupy the memory.
why?
any thought is appreciate.
Your code creates a set of closures. This prevents the release of memory. In your example the memory will be released after the completion of all timeouts.
This can be seen (after 100 seconds):
var index = 0;
var timeout;
function leak() {
index++;
timeout = setTimeout(leak, 0);
}
leak();
setTimeout(function() {
clearTimeout(timeout);
}, 100000);
setInterval(function() {
console.log(process.memoryUsage());
}, 2000);

Resources