Reputation: 12455
The answer from Why shouldn't I use global variables in JavaScript for something that's constant? listed 8 issues with global variables, the # 2 is
If you have any asynchronous code that modifies globals or timer-driven code that modifies globals and more than one asynchronous operation can be in flight at the same time, the multiple async operations can step on each other through the modification of the same globals.
While the statement is easy to understand I came across a strange socket.io problem I can't figure out why: I find that if one global socket.io client is used for 2 connections (wrongly), the 2nd connection gets the 1st connection message. I create a sample project https://github.com/qiulang/2sockets to demonstrate the problem.
The server logic is simple, when client connects it needs to send a login message with user_id , if the server finds the same user_id login with a different socket.id, it decides that this is the case that the same user login from a different client so it will emit logoff message to the first socket.io client. When the client gets logoff message it will close its connection.
let records = {}
io.on("connection", (socket) => {
socket.on("login",({user_id,client}) =>{
let old_socket = records[user_id]
if ( old_socket ) {
log(`The same ${user_id} with ${old_socket} has logged in, let him log off first`)
io.to(old_socket).emit('logoff', 'another login')
}
records[user_id] = socket.id
})
socket.on("disconnect",(reason) => {
log(`${socket.id} disconnected with reason ${reason}`)
})
});
The client uses a global variable instead of function scope variables. But in main() there are 2 connections, then the second connection gets the logoff message wrongly.
function setupWS(client) {
// let socket = io(server, { transports: ['websocket']}) //Should use a function scope variable
socket = io(server, { transports: ['websocket']}) //wrong!! Should NOT use a global variable
socket.on('connect', function () {
log(`${client} connected to local ws server with ${socket.id}`)
socket.emit('login', { user_id, client })
})
socket.on('logoff', (msg) => {
log(`${socket.id}:${client} should be kicked off for: ${msg}`)
socket.close()
})
}
function main() {
setupWS('nodejs')
setTimeout(()=> {
log('open another connection in 5 seconds')
setupWS("browser")
},5000)
}
main()
When run the code from the client side, I will see log like,
nodejs connected to local ws server with ijqTzPl2SXHmB-U0AAAC
browser connected to local ws server with l7vCcbeOmVU5d_TSAAAE
l7vCcbeOmVU5d_TSAAAE:nodejs should be kicked off for: another login
From the server side, I will see log like,
The same 1_2_1000 with ijqTzPl2SXHmB-U0AAAC has logged in, let him log off first
l7vCcbeOmVU5d_TSAAAE disconnected with reason client namespace disconnect
So the server correctly sent to the 1st socket.id ijqTzPl2SXHmB-U0AAAC
the logoff message while at the client side the log is l7vCcbeOmVU5d_TSAAAE
:nodejs (NOT l7vCcbeOmVU5d_TSAAAE
:browser) should be kicked off. And it is indeed the 2nd socket.id l7vCcbeOmVU5d_TSAAAE called close()
--- update ---
With jfriend00 answer I understand my problem. What I want to add is this problem was introduced by #6 problem in his answer Why shouldn't I use global variables in JavaScript for something that's constant?
A simple omission of the keyword "var" on a local variable makes it a global variable and can confuse the heck out of code
Normally we require each node.js file adding "use strict"
the first line, but now I realize running node --use_strict
is much safer.
BTW, if I may add one more problem of using global variables to his excellent answer there, I will add To figure out where and how global variables are modified is quite painful.
Upvotes: 0
Views: 1009
Reputation: 707826
When you call setupWs()
the second time, it overwrites the global socket
variable. Then, when a message comes in to the first connection, you log socket.id
, but you aren't logging the connection that actually just got the message, you're logging from the socket
global variable which now points to the 2nd connection.
So, your logging is at fault here. It will log socket.id
from the same socket
global variable, no matter which connection is actually getting a message. So, faulty logging is making it appear that a different connection is getting the message than is actually the case.
And, in addition to the logging, the two places you use socket
inside a message handler are also referring to the wrong socket. So, you need to store the socket locally. If you want global access to the last socket you created, you can also store it globally.
function setupWS(client) {
const activeSocket = io(server, { transports: ['websocket']});
// store the latest socket globally so outside code can send
// messages on the latest socket we connected
socket = activeSocket;
activeSocket.on('connect', function () {
log(`${client} connected to local ws server with ${activeSocket.id}`)
activeSocket.emit('login', { user_id, client })
})
activeSocket.on('logoff', (msg) => {
log(`${activeSocket.id}:${client} should be kicked off for: ${msg}`)
activeSocket.close()
})
}
And, as you have seen and apparently know already, this is an example of the kind of problem using a global variable can create.
Upvotes: 1