sauce sauce - 3 months ago 40
Node.js Question

Socket.io Leak - How do I remove this mongoose event listener that is registered onConnect?

I have the following subscribe even happening when a user connects via socket.io:

socket.on('subscribe', function(room) {
console.log('joining room', room.id);
socket.join(room.id);
socket.roomName = room.id;

// Insert sockets below
require('../api/item/item.socket').register(socket);
});


The "item.socket" code attaches mongoose event handlers for db events (save, delete, etc..) and emits socket messages when they happen. Here is that code:

var Item = require('./it');

exports.register = function(socket) {
Item.schema.post('save', function (doc) {
console.log("hit save message");
onSave(socket, doc);
});
Item.schema.post('update', function (doc) {
console.log("hit update message");
onSave(socket, doc);
});
Item.schema.post('remove', function (doc) {
onRemove(socket, doc);
});
};

function onSave(socket, doc, cb) {
socket.emit(doc._id + ':item:save', doc);
}

function onRemove(socket, doc, cb) {
socket.emit(doc._id + ':item:remove', doc);
}


When a client disconnects the following code is executed:

function onDisconnect(socket) {
console.log('disconnected: ' + socket.roomName);
socket.leave(socket.roomName);
socket.removeAllListeners("subscribe");
}


The basic problem that i'm having is, if a user connects the get that item.socket mongoose handler to send them updates about model changes. When they disconnect (reload page, leave come back, etc..) that even handler never goes away. For instance, I make a change to "item" and I'll get 10 "hit save" messages if I reload the page 10 times.

Ideally when a client leaves the page, that mongoose handler would be destroyed so its not trying to send messages to no one.

EDIT: It's very clear to me that this is an event handler leak. And basically I need to do something to destroy the handler on disconnect. I need to somehow create a reference to
require('../api/item/item.socket').register(socket);
and then on disconnect unregister it but i can't figure out how to properly do that with the mongoose models and the way i have it set up.


function onDisconnect(socket) {
console.log('disconnected: ' + socket.roomName);
socket.leave(socket.roomName);
socket.removeAllListeners("subscribe");
//destroy handler here
}


Answer

Thanks for the insight, sometimes you just stare at a problem to long and realize the answer is straight forward. I pretty much did what you said, moved to only register the item.socket once. However instead of keeping a list of sockets around, we just use the socket.io way to just to send messages to the right sockets, code as follows:

'use strict';

var Item = require('./item.model');

var log4js = require('log4js');
var logger = log4js.getLogger();

exports.register = function(socketio) {
  Item.schema.post('save', function (doc) {
    logger.debug("hit save message " + doc._id);
    onSave(socketio, doc);
  });
  Item.schema.post('update', function (doc) {
    logger.debug("hit update message");
    onSave(socketio, doc);
  });
  Item.schema.post('remove', function (doc) {
    onRemove(socketio, doc);
  });
};

function onSave(socketio, doc, cb) {
  socketio.sockets.in(doc._id).emit(doc._id + ':item:save', doc);
}

function onRemove(socketio, doc, cb) {
  socketio.sockets.in(doc._id).emit(doc._id + ':item:remove', doc);
}

Where doc._id is the room a client joins when subscribing. Leak fixed!!! Thanks :-)