2

I've created a simple nodejs server and client. They interact with each other via 15000 tcp sockets.

client code:

'use strict';

const net = require('net');

for (let i = 0; i < 15000; ++i) {
    let socket = new net.Socket();
    socket.connect(6000, '127.0.0.1', () => {
        console.log('Connected');
        socket.write('data to server');
    });

    socket.on('data', data => {
        console.log(data);
    });

    socket.on('close', () => {
        console.log('Connection closed');
    });
}

server code:

'use strict';

const net = require('net');

let sockets = [];

let server = net.createServer(socket => {
    socket.write('blabla from server');
    socket.on('data', data => {
        console.log(data);
    });
    sockets.push(socket);
    if (sockets.length >= 15000) {
        setTimeout(() => {
            console.log('cleanup start');
            for (let socket of sockets) {
                socket.end();
                socket.destroy();
                socket.unref();
            }
            console.log('cleaned up and ready');
        }, 80000);
    }
});

if (global.gc) {
    setInterval(() => {
        global.gc();
    }, 15000);
}

setInterval(() => {
    console.log(process.memoryUsage());
}, 5000);

server.listen(6000, '127.0.0.1');

They send and receive messages. During creation of sockets the memory usage gets high. But after destroying the sockets I expect the memory usage to get low, which doesn't happen.

Ara Yeressian
  • 3,746
  • 3
  • 26
  • 36

2 Answers2

1

You have an array which is full with null values.

sockets.push(socket);

if (sockets.length >= 15000) {   // The length is never decreasing
    setTimeout(() => {
        for (let socket of sockets) {
            [...]
            socket = null;    // Because you just null the value but the array has still the same length.
        }

    }, 80000);
}

Have a look into this example:

var arr=[];

setInterval(function(){
    arr.push(null);
}, 1);

setInterval(() => {
    console.log(process.memoryUsage());
    console.log(arr.length);  // You will see the array is growing continously
}, 5000);

So the solution is to choose a different iteration numeric and slice they keys free the array index instead of overwriting it with another property.

But why are you deleting all connections regardles if these are in use or not. You can destroy the socket better on clients disconnect.

sockets.slice(index,1);
Bernhard
  • 4,855
  • 5
  • 39
  • 70
  • I don't push values to array in interval see my code. Anyway even if arrays of 15000 null values reside on the server which is not since the reference will be lost after setTimeout complete and GC will remove them, it should not take 20 MB of RAM. Which it does in this case. – Ara Yeressian Nov 20 '15 at 21:41
  • @AraYeressian the interval is just for demonstration that an array of null values needs memory. When you're removing the references and count the array length does it decrease even after a global.gc() call? using global.gc is bad practice because of v8's sweeping behavior. http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection http://stackoverflow.com/a/30654451/1165289 – Bernhard Nov 21 '15 at 10:42
  • I don't plan to use in global.gc in production. I use it here for testing purpose. As I said the reference to entire array gets lost after setTimeout so it doesn't matter if you set its elements to null or not. Anyway you can check it yourself just remove the socket = null; from code and see the result yourself. – Ara Yeressian Nov 23 '15 at 07:40
1

Ok it seems I found the solution. Nodejs caches the Socket objects to be used in future. Here is the updated code.

server code:

'use strict';

const net = require('net');

let sockets = [];

let numberOfSockets = 2000;
let server = net.createServer(socket => {
    sockets.push(socket);
    if (sockets.length >= numberOfSockets) {
        console.log(process.memoryUsage());
        console.log('cleanup start');

        for (let socket of sockets) {
            socket.end();
            socket.destroy();
            socket.unref();
        }
        sockets = [];
        global.gc();
        setTimeout(() => {
            console.log(process.memoryUsage());
        }, 1000);

        console.log('cleaned up and ready');
    }
});

if (global.gc) {
    setInterval(() => {
        global.gc();
    }, 15000);
}

server.listen(6000, '127.0.0.1');

client code:

'use strict';

const net = require('net');

let numberOfClosedSockets = 0;
let numberOfSockets = 2000;

function test() {
    for (let i = 0; i < numberOfSockets; ++i) {
        let socket = new net.Socket();
        socket.connect(6000, '127.0.0.1', () => {
            console.log('Connected');
        });

        socket.on('data', data => {
            console.log(data);
        });

        socket.on('close', () => {
            console.log('Connection closed ' + numberOfClosedSockets);
            numberOfClosedSockets++;
            if (numberOfClosedSockets >= numberOfSockets) {
                numberOfClosedSockets = 0;
                setTimeout(() => {
                    test();
                }, 2000);

            }
            socket.destroy();
            socket.unref();
        });
    }
}
test();

In this example the client creates new sets of sockets after the first sets of sockets has been destroyed. And it does it over and over again. If you look at memory usage it doesn't increase so there is no memory leak.

github issue link

Ara Yeressian
  • 3,746
  • 3
  • 26
  • 36