Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Socket.io disconnect events and garbage collection for related closure

I have a basic real-time server that uses socket.io. My question involves closures and garbage collection as well as whether I should bother storing socket connections in an associative array or just leave it to the closure to manage the connections.

One question I have is if a socket connection disconnects, will the same socket connection attempt to send a message if emit is invoked on the disconnected socket? In other words, what happens when you call socket.emit() on a disconnected socket?

Here's some code, questions at the bottom:

 var socketio = require('socket.io');
 var io = socketio.listen(server);
 var EE = require('events').EventEmitter;
 var ee = new EE(); //this is actually initialized elsewhere but now you know what it is

 var connectedUsers = {}; //associative array to store socket connections by socket.id

        io.on('connection', function (socket) {

           connectedUsers[socket.id] = socket;  //should I bother storing the socket connections in the associative array or just leave it to the closure to store them

            socket.on('disconnect', function () {
                connectedUsers[socket.id] = null; //should I bother removing
            });

            ee.on('update',function(data){

                socket.emit('update',JSON.stringify(data));

            });

            ee.on('insert',function(data){

                socket.emit('insert',JSON.stringify(data));

            });

            ee.on('delete',function(data){

                socket.emit('delete',JSON.stringify(data));

            });
        });
    }

So my primary question/concern is that the amount of memory used for the closures will grow linearly with the number of socket connections and will never stop or decrease. Do socket.io disconnect events release memory via garbage collection?

It seems that I will need to reorganize the code above to accomplish two things:

  1. to allow for garbage collection so that the server memory footprint doesn't grow unboundedly

  2. to avoid publishing all the data for each and every socket connection, which is what it is doing now.

Is this accurate?

like image 610
Alexander Mills Avatar asked Jul 28 '15 23:07

Alexander Mills


1 Answers

One question I have is if a socket connection disconnects, will the same socket connection attempt to send a message if emit is invoked on the disconnected socket? In other words, what happens when you call socket.emit() on a disconnected socket?

No. Any calls to .emit() on a disconnected socket are ignored. Here's the relevant source code snippet.

connectedUsers[socket.id] = socket;
//should I bother storing the socket connections in the
// associative array or just leave it to the closure to store them

You should not store them. Leave it to the closure. Then no need to set them to null later.

So my primary question/concern is that the amount of memory used for the closures will grow linearly with the number of socket connections and will never stop or decrease. Do socket.io disconnect events release memory via garbage collection?

Yes, things will be released for garbage collection as sockets disconnect. socket.io will prune them from its internal queues, nothing will have a reference to them anymore, and they will be eligible for garbage collection. HOWEVER, your code above is incorrect with how you send out events. See below for details.

ee.on('update',function(data){
   socket.emit('update',JSON.stringify(data));
});

Don't do this pattern this way. The problem is you never call removeListener so on every 'update' event you will execute one of these callbacks for every socket that has ever connected since the process started, even disconnected ones. That's your leak. Instead, for this pattern, just set up your broadcasts once and only once outside of the closure:

ee.on('update', function (data) {
  io.sockets.emit('update', data);
});

(aside: the JSON.stringify is almost certainly unnecessary. Only do that if you really know you must get it as a string on the other side. Just emitting an object is probably the correct thing)

Now for the case where you need to send to a specific socket as opposed to all connected sockets, you should use a named function so you can pair each ee.on with the corresponding ee.removeListener to properly prevent phantom listeners.

io.on('connection', function(socket) {
  // every socket gets a closure
  // and a distinct onUpdate function object
  function onUpdate(data) {
    socket.emit('update', data);
  }
  ee.on('update', onUpdate);

  socket.on('disconnect', function() {
    // removes the handler for this specific socket,
    // leaving the others intact
    ee.removeListener('update', onUpdate);
  });
});
like image 171
Peter Lyons Avatar answered Oct 16 '22 06:10

Peter Lyons