33

I appear to have a memory leak with my Node.js application. I built it quickly, and my JavaScript isn't too strong, so this might be easy.

I've done some heap dumps on it, and it's the String object? leaking memory, at the rate of about 1MB every 5 minutes. I expanded String, and it's actually String.Array?

Heap stack: enter image description here

#!/usr/local/bin/node

var port = 8081;

var io = require('socket.io').listen(port),
sys = require('sys'),
daemon = require('daemon'),
mysql = require('mysql-libmysqlclient');

var updateq = "SELECT 1=1";
var countq = "SELECT 2=2";

io.set('log level', 2);


process.on('uncaughtException', function(err) {
  console.log(err);
});

var connections = 0;

var conn = mysql.createConnectionSync();
dbconnect();

io.sockets.on('connection', function(client){ 
  connections++;
  client.on('disconnect', function(){ connections--;  }) 
});

process.on('exit', function () {
    console.log('Exiting');
    dbdisconnect();
});

function dbdisconnect() {
     conn.closeSync();
}

function dbconnect() {
    conn.connectSync('leet.hacker.org','user','password');
}


function update() {
    if (connections == 0)
        return;
    conn.query(updateq, function (err, res) {
      if (err) {
        dbdisconnect();
        dbconnect();
        return;
      }
      res.fetchAll(function (err, rows) {
        if (err) {
          throw err;
        }
        io.sockets.json.send(rows);
      });
    });
}

function totals() {
    if (connections == 0)
        return;
        conn.query(countq, function (err, res) {
          if (err) {
        // Chances are that the server has just disconnected, lets try reconnecting
        dbdisconnect();
        dbconnect();
            throw err;
          }
          res.fetchAll(function (err, rows) {
            if (err) {
              throw err;
            }
        io.sockets.json.send(rows);
          });
        });

}

setInterval(update, 250);
setInterval(totals,1000);

setInterval(function() {
console.log("Number of connections: " + connections);
},1800000);



  daemon.daemonize('/var/log/epiclog.log', '/var/run/mything.pid', function (err, pid) {
    // We are now in the daemon process
    if (err) return sys.puts('Error starting daemon: ' + err);

    sys.puts('Daemon started successfully with pid: ' + pid);
  });

Current version

function totals() {

        if (connections > 0)
        {
                var q = "SELECT query FROM table";

            db.query(q, function (err, results, fields) {
            if (err) {
                    console.error(err);
                    return false;
            }

            for (var row in results)
            {
                    io.sockets.send("{ ID: '" + results[row].ID + "', event: '" + results[row].event + "', free: '" + results[row].free + "', total: '" + results[row].total + "', state: '" + results[row]$
                    row = null;
            }


            results = null;
            fields = null;
            err = null;
            q = null;
            });
    }
}

Still leaking memory, but it seems only on these conditions:

  • From startup, with no clients -> Fine
  • 1st client connection -> Fine
  • 2nd client (even with the 1st client disconnecting and reconnecting) -> Leaking memory
  • Stop all connections -> Fine
  • 1 new connection (connections = 1) -> Leaking memory
hong4rc
  • 3,999
  • 4
  • 21
  • 40
giggsey
  • 933
  • 2
  • 11
  • 31
  • 1
    Informative: http://stackoverflow.com/questions/5733665/how-to-prevent-memory-leaks-in-node-js – j.w.r Jun 24 '11 at 16:43
  • 7
    `connectSync` :( – Raynos Jun 24 '11 at 16:47
  • You mention you modified String to contain String.Array, and even point us to that in your memory snapshot, but I see nothing in your code that would use this, nor what you actually did to create String.Array – Matt Jun 24 '11 at 16:48
  • @Raynos: I know, I need to find a better MySQL client. – giggsey Jun 24 '11 at 19:53
  • @Matt, I'm not using String.Array. That's just what I noticed was increasing in memory from the heap snapshots. – giggsey Jun 24 '11 at 19:53
  • When you say it increases at a rate of 1MB / 5 minutes, how many connections is that? If you increase the # of connections, does the leak grow faster? How about in proportion to the # of messages sent from the client? – Matt Jun 24 '11 at 20:08
  • @Matt - I was running it in debug mode, so only one connection (me). – giggsey Jun 27 '11 at 08:17
  • @Matt - Client doesn't send any messages back to the server. As for Server -> Client: update() generates around 10-20 messages per 'tick'. totals() is around 25 messages per second. – giggsey Jun 27 '11 at 08:23
  • I've compared heapstacks at the start and end of both of my functions, and they are not increasing in memory. So it must be from a component I'm using. – giggsey Jun 27 '11 at 09:29
  • **Updates**: I replaced the mysql-libmysqlclient with just mysql (npm package), and no changes there. I have noticed when it increases though: When the first user connects, memory usage is fine, and it's great When this user reconnects, or another user connects, we start leaking All users disconnect, it's fine 1 new person connects, and we're leaking again So I'm thinking it's something to do with socket.io. I'm using the new version, that was released last week. – giggsey Jun 27 '11 at 16:46
  • I would suspect JSON serialization to be at least related to the leak - did you running it without `io.sockets.json.send`? – Wladimir Palant Jun 28 '11 at 07:45
  • @Wladimir Palant - Tried that (just sending the json as a string), and it still leaks memory. I've edited the OP with the current copy of one of the functions. – giggsey Jun 28 '11 at 09:01
  • 5
    @giggsey sounds like a socket.io leak. Go file a bug – Raynos Jun 28 '11 at 16:22
  • For all: I've filed a bug report with the Socket.IO guys, and they are looking into it: https://github.com/LearnBoost/socket.io/issues/299 – giggsey Jun 30 '11 at 06:57

2 Answers2

5

Do yourself a favour and use node-mysql, it's a pure javascript mysql client and it's fast. Other than that, you should be using asynchronous code to stop IO being blocked whilst you're working. Using the async library will help you here. It has code for waterfall callback passing among other things.

As for your memory leaking, it probably isn't socket.io, although I haven't used it in a few months, I have had many thousands of concurrent connections and not leaked memory, and my code wasn't the best either.

Two things, however. Firstly your code is faily unreadable. I suggest looking into properly formatting your code (I use two spaces for every indentation but some people use four). Secondly, printing the number of connections every half an hour seems a little silly, when you could do something like:

setInterval(function() {
  process.stdout.write('Current connections: ' + connections + '     \r');
}, 1000);

The \r will cause the line to be read back to the start of the line and overwrite the characters there, which will replace the line and not create a huge amount of scrollback. This will help with debugging if you choose to put debugging details in your logging.

You can also use process.memoryUsage() for quickly checking the memory usage (or how much node thinks you're using).

Robin Duckett
  • 2,094
  • 3
  • 16
  • 15
  • 1
    Thanks. Although it is currently believed to be a memory leak in socket.io - See: https://github.com/LearnBoost/socket.io/issues/299 – giggsey Jul 08 '11 at 08:33
0

Could this be related to the connected clients array not clearing properly when a client disconnects? The array value gets set to NULL rather than being dropped from the array.

imperium2335
  • 23,402
  • 38
  • 111
  • 190