I've got a code setup and I would like to know if it gets garbage-collected so my system won't start swapping and freeze/crash.
The setup should be able to run an undetermined amount of time assuming you stay within the normal parameters of your system (e.g. so don't connect 1.000.000 users to one single thread process all at the same time)
I've made a simplified version.
// connection.js
var io, Handler;
io = require('socket.io');
Handler = require('./handler');
io.on('connection', function (socket) {
new Handler({socket: socket})
});
// handler.js
var Handler,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
function Handler(arg) {
this.socket = arg.socket;
this.test = bind(this.test, this);
console.log('Handler', 'New connection', this.socket.id);
this.listeners();
}
Handler.prototype.listeners = function() {
return this.socket.on('test', this.test);
};
Handler.prototype.test = function() {
return console.log('SocketIO', 'test', this.socket.id);
};
module.exports = Handler;
So is 'new Handler' garbage-collected after a 'socket disconnect event' has occurred?
If not how should I change the code?
At first I thought this might solve the problem
var io, connections, Handler;
io = require('socket.io');
Handler = require('./handler');
connections = {}
io.on('connection', function (socket) {
connections[socket.id] = new Handler({socket: socket});
socket.on('disconnect', function (){
connections[socket.id] = null
})
});
But this only creates a reference to the 'new Handler' and removes that same reference on disconnect. That doesn't necessarily mean the 'new Handler' will be removed as well. It could still be listening for and/or doing stuff.
Your Handler object should get garbage collected as long as no other code of yours is keeping a reference to it.
When the actual socket itself is closed, the system knows that the event handlers are no longer live and thus they don't prevent garbage collection. The browser does the same sort of thing when you remove a DOM object that has event handlers on it so that these event handlers don't prevent garbage collection.
So, when the socket is closed and socket.io releases any references it has to the Javascript socket object, there should be no code that has any references to the Handler object any more and it will be eligible for garbage collection.
If you make your own reference to the Handler object such as:
var connections = {};
io.on('connection', function (socket) {
connections[socket.id] = new Handler({socket: socket});
});
Then, you will have to clear that reference upon disconnect as in the code below. Otherwise, this reference will stay alive and will keep the Handler object from being garbage collected.
var connections = {};
io.on('connection', function (socket) {
connections[socket.id] = new Handler({socket: socket});
socket.on('disconnect', function() {
delete connections[socket.id];
})
});
Note: I'd recommend you actually use delete to remove the property rather than just setting it to null. That way you won't get unused properties building up on the connections object.
Related
From my understanding, a socket.io server manages N connections between itself and N clients via N separate sockets, each with its own ID.
It therefore makes sense that you get two different ID's when opening two tabs.
Consider, however, this minimal example:
index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"></html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<title>Stuffy Stuff</title>
</head>
<body>
<button id="btnA">A</button>
<script src="/socket.io/socket.io.js"></script>
<script src="app.js"></script>
</body>
index.js
var express = require('express');
var path = require('path');
var app = express();
var servercode = require('./server');
app.use(express.static(path.join(__dirname,'public')));
var server = require('http').createServer(app).listen(process.env.PORT || 8080);
var io = require('socket.io').listen(server);
io.on('connection', function (socket)
{
console.log('client ' + socket.id + ' connected');
servercode.init(io, socket);
});
app.js
;
jQuery(function($) // client-side code
{
socket = io.connect();
$(document).on('click', '#btnA', stuff);
function stuff()
{
socket.emit('dostuff', socket.id);
socket.on('answerstuff', function()
{
console.log("Answered");
});
}
}($));
server.js
// Server-side
var io;
var socket;
exports.init = function(sio, sock)
{
io = sio;
socket = sock;
socket.on('dostuff', doStuff);
};
function doStuff(idd)
{
console.log('who is asking: ' + idd);
console.log('who am I answering to: ' + socket.id);
console.log(socket.id);
socket.emit('answerstuff');
}
Now say I fire up node index.js and then open two instances of localhost:8080; let's call them A and B. If I press the button on A's instance, the 'dostuff' message is emitted, it's caught by the server, but the socket it answers to is the one relative to B's instance, not A's. In general, it answers to the most recent instance. Why is it so?
EDIT: explanation
As I've finally grasped the nature of my mistake I thought I could help whoever runs into a similar issue.
Let's take our sample workflow: A connects, B connects, A presses button.
server.init(io, socket A) gets called
init(socket A) attaches the dostuff event, with callback function doStuff, to socket A
B connects... init attaches dostuff to socket B
Client A presses button, thus A emits dostuff passing socket A's id
doStuff(idd) is executed, but its definition contains a reference to a variable (socket) not defined within the function's scope, therefore it has to navigate its parent scopes
Such reference is found: it's socket = sock (if we omit var, the declaration 'bubbles up' in scope until it finds a variable with such a name. Had we written var socket = sock INSIDE init, the function couldn't have found any such variable)
But socket points to socket B thanks to B connecting and init being executed again. Therefore, the function only knows socket B.
In order to solve this issue we need to employ closures: functions that encapsulate the state of the world at the moment of their definition. In other words, a closure is an object containing the function definition AND the value of the variables not in the function scope but referenced by it.
As illustrated in the solution given by JagsSparrow, there are two ways of dealing with this issue and both involve creating a closure:
1. Use the function.bind(thisArg, args) function
The bind() function returns the function 'A' it's called upon where this is bound to thisArg (where it would normally point to the object calling A) and instantiates whatever arguments are specified in args.
In our case we don't need this as it's never mentioned in the doStuff() function, but we do need it to remember whose socket is the one mentioned in the arguments. We can do this by writing
doStuff.bind(null, socket)
This expression returns an object that is the doStuff() function where, in its context, this equals null (we could've written doStuff.bind(this, socket): in that case, this equals init) and its first argument, socket, is bound to socket. Therefore,
socket.on('dostuff', doStuff.bind(this,socket));
tells socket A to fire up doStuff (which contains a reference to socket A) when dostuff happens. Same with socket B and any other socket.
2. Use a nested function
We simply move the definition of function doStuff(idd) inside socket.on():
socket.on('dostuff', function doStuff(idd)
{
console.log('who is asking: ' + idd);
console.log('who am I answering to: ' + socket.id);
console.log(socket.id);
socket.emit('answerstuff');
});
This way, again, the socket is bound to a function that contains (by virtue of the closure) the definition of whichever socket is performing this operation.
In server.js you declared var socket; which is local of the server.js module (module scope),
So this variable gets assigned to different client each time its get connected
When client A get connected socket is of client A.
When client B get connected socket is of client B.
So, Don't make socket variable local to server.js module
Solution 1 :
server.js
var io;
//var socket;
exports.init = function(sio, sock)
{
io = sio;
socket = sock;
socket.on('dostuff', doStuff.bind(this,socket));
};
function doStuff(socket,idd)
{
console.log('who is asking: ' + idd);
console.log('who am I answering to: ' + socket.id);
console.log(socket.id);
socket.emit('answerstuff');
}
Solution 2 :
server.js
var io;
//var socket;
exports.init = function(sio, sock)
{
io = sio;
var socket = sock;
socket.on('dostuff', function doStuff(idd)
{
console.log('who is asking: ' + idd);
console.log('who am I answering to: ' + socket.id);
console.log(socket.id);
socket.emit('answerstuff');
});
};
This is more of a best practices question I assume, but in the following, can anyone tell me how I would pass a reference of the socket object outside of the method handler it is in?
io.on('connection', function (socket) {
console.log('connection '+socket);
//do stuff here
});//end callback handler
//need a reference to socket here!!
Just assign it to a variable defined in that scope?
var globalSocket;
io.on('connection', function (socket) {
globalSocket = socket;
});
// now what?
However, I don't see a reason to do this since you won't be able to use the value until the event handler was called (which you only know when the event handler was called).
To solve that, you would need to put the code that needs access to the socket in a function and call the function from the event handler:
var globalSocket;
io.on('connection', function (socket) {
globalSocket = socket;
doSomething();
});
function doSomething() {
// do something with globalSocket
console.log(globalSocket);
}
but then it would be much more elegant to pass socket as argument to the function:
io.on('connection', function (socket) {
doSomething(socket);
});
function doSomething(socket) {
// do something with socket
console.log(socket);
}
Well, you have to store this in some sort of socket connection pool. But keep in mind that if you want to distribute in several process/machines with loadbalancers, this will not work very well.
io.on('connection', function(socket) {
socket.join('myRoom');
if(condition){
bindFunctions();
}
});
Let's say condition is true when the 4th socket connects. The code below successfully binds a .on handler to all the sockets in myRoom.
bindFunctions(){
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
var socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
//This is always the same property, no matter who calls this function.
});
}
}
However, when 'my event' fires, regardless of which client triggered it, the socket object is always the 4th socket that connected. It is always the socket that called bindFunctions. I would have thought that the handler socket should be the same as the socket inside the function.
So I have two questions:
Why is the socket inside the 'my event' function the socket that called bindFunctions instead of the socket that fired the event?
What can I do instead to be able to access properties of the socket that called 'my event' whilst inside the function?
That happens because although you're doing var socket = io.sockets.connected[socketId]; inside the loop, in Javascript, the var keyword is function scoped, and not block scoped.
Under the guts, what really happens is:
bindFunctions(){
var socket;
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
//This is always the same property, no matter who calls this function.
});
}
}
So, since your event will happen later, your socket variable is always the last one set.
To prevent this behavior, if using Node.js version > 4.0.0, you can use const instead of var, because it's block scoped. But in Node.js, to be able to use const and let keywords block-scoped, you must put 'use strict'; in the begining of your function, e.g:
bindFunctions(){
'use strict';
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
const socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
//This is always the same property, no matter who calls this function.
});
}
}
Another way of solving your problem is by creating an IIFE, so you always pass the current variable to the event, e.g:
bindFunctions(){
for(var socketId in io.sockets.adapter.rooms["myRoom"]){
var socket = io.sockets.connected[socketId];
(function(socket) {
socket.on('my event',function(submit){
console.log(socket.myVariable);
});
})(socket);
}
}
Doing that, the socket variable inside the IIFE will always refer to the object passed as parameter, and you'll get your desired behavior.
You can also use Object.keys combined with Array.prototype.forEach instead of for...in loop:
bindFunctions(){
var room = io.sockets.adapter.rooms["myRoom"];
Object.keys(room).forEach(function(key) {
var socketId = room[key];
var socket = io.sockets.connected[socketId];
socket.on('my event',function(submit){
console.log(socket.myVariable);
});
});
}
Doing that, you're creating a new context each iteration, since it's a callback function being called, and you won't have problems with function scoping.
io.sockets.on('connection', function (socket) {
socket.on('something', function(data){
var VAR1 = true;
if(VAR1 === true){
VAR1 = false;
}
});
});
Is the variable "VAR1" unique for every user who triggers the socket event "something"? Let's say that there are 1000 users will every connections to the socket event's variable start with VAR1 being true, and can others alter it? Is it unique to every connections?
Your VAR1 is unique for every single .on('something') event that occurs and will be lost as soon as the event handler is over. It is a local variable inside that event handler so a new VAR1 is created every single time the event handler is called and it will be garbage collected by Javascript as soon as the event handler has finished running. The next time the event handler is triggered, a new VAR1 will be created and then garbage collected.
Now, if you wanted it to be unique for each separate connection and last for the duration of that connection, you could declare it at a slightly different higher scope like this:
io.sockets.on('connection', function (socket) {
var VAR1 = true;
socket.on('something', function(data){
if(VAR1 === true){
VAR1 = false;
}
});
});
Now, a new VAR1 will be created for each socket that connects and that variable will last for the duration of that connection (because it's in a closure) and each .on('something', ...) event that occurs will be able to access a unique VAR1 for each separate socket.
FYI, if you want a unique variable for a socket, you can also just add a property to the socket object iself:
io.sockets.on('connection', function (socket) {
socket.VAR1 = true;
socket.on('something', function(data){
if(socket.VAR1 === true){
socket.VAR1 = false;
}
});
});
I just wanted to confirm a suspicion of mine.
I stumbled across an article which recommended using Socket.io in the following fashion:
var app = require('express').createServer()
var io = require('socket.io').listen(app);
app.listen(8080);
// Some unrelated stuff
io.sockets.on('connection', function (socket) {
socket.on('action1', function (data) {
// logic for action1
});
socket.on('action2', function (data) {
// logic for action2
});
socket.on('disconnect', function(){
// logic for disconnect
});
});
I feel like the following would be a better use of resources:
var app = require('express').createServer()
var io = require('socket.io').listen(app);
app.listen(8080);
// Some unrelated stuff
io.sockets.on('connection', function (socket) {
socket.on('action1', action1);
socket.on('action2', action2);
socket.on('disconnect', disconnect);
});
function action1(data) {
// logic for action1
}
function action2(data) {
// logic for action2
}
function disconnect() {
// logic for disconnect
}
My feeling is that although the anonymous function that handles the connection event is only created in memory once, the anonymous functions that handle action1, action2, and disconnect are created in memory for every socket connection. The issue with the second approach is that socket is no longer in scope.
So firstly, is my suspicion about the creation of functions true? And secondly, if so is there a way to get socket in scope for the named functions?
Using a closure helps to keep the scope clean:
io.sockets.on('connection', function () {
function action1(data) {
// logic for action1
}
function action2(data) {
// logic for action2
}
function disconnect() {
// logic for disconnect
}
return function (socket) {
socket.on('action1', action1);
socket.on('action2', action2);
socket.on('disconnect', disconnect);
}
}()); // <- note the immediate function call
To your questions:
So firstly, is my suspicion about the creation of functions true?
Yes. The closure approach above prevents this, the callback functions are created only once. Plus: all see the correct parent scopes.
And secondly, if so is there a way to get socket in scope for the named functions?
The socket will be available as this in the callbacks.
You're correct, that the anonymous methods are created for each connection - and if you don't need scope, then the second method does avoid that. If you need the socket scope there's no real way to avoid it. If you want to keep the methods external (for some other reason) and still keep scope, you could always:
//...
socket.on('action1', function(){
action1.apply( socket, arguments );
} );
//... and so forth.
But that has you back to creating a method signature for each connection, so I'm not sure what you'd be gaining.