Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manager.socket() re-uses a manually disconnected socket without reconnecting it #1460

Closed
AntonNeld opened this issue Apr 7, 2021 · 8 comments
Labels
bug Something isn't working
Milestone

Comments

@AntonNeld
Copy link

Describe the bug
When calling Manager.socket('/someNamespace') a second time after already having called it before, it will re-use the same socket. If that socket has been manually disconnected using Socket.disconnect(), it will be returned in a disconnected state.

To Reproduce

Please fill the following code example:

Socket.IO server version: 3.1.1

Server

const { Server } = require('socket.io');

const io = new Server(3000, {});

io.of('/someNamespace').on('connection', (socket) => {
  console.log(`connect ${socket.id}`);

  socket.on('disconnect', () => {
    console.log(`disconnect ${socket.id}`);
  });
});

Socket.IO client version: 3.1.1

Client

const io = require('socket.io-client');

const manager = new io.Manager('ws://localhost:3000');

const firstSocket = manager.socket('/someNamespace');
firstSocket.on('connect', () => {
  console.log('Connected first time');
  firstSocket.disconnect();
  console.log(firstSocket.connected); // false

  const secondSocket = manager.socket('/someNamespace');
  secondSocket.on('connect', () => {
    console.log('Connected second time'); // Never happens
  });
});

Expected behavior
I expected Manager.socket('/someNamespace') to return a socket that connects to the server. Either the old socket that tries to connect again, or a fresh socket.

Additional context
The context is a single page application where not all namespaces are relevant to all "pages" in the application, so we disconnect from a page-specific namespace when leaving a page. The reason for explicitly creating a Manager instance is to avoid creating a new underlying connection when we reconnect to the namespace.

Our current workaround is to manually call Socket.open() after Manager.socket().

@AntonNeld AntonNeld added the bug Something isn't working label Apr 7, 2021
@darrachequesne
Copy link
Member

Thanks, I could indeed reproduce.

Since we cannot have two (or more) active Socket instances for the same namespace and the same Manager, the most reasonable behavior would be to call Socket.connect() on the cached Socket instance, depending on the autoConnect option.

What do you think?

Note: this behavior is present since forever: https://github.com/socketio/socket.io-client/blob/1.0.0/lib/manager.js#L278-L289

@AntonNeld
Copy link
Author

I agree, that sounds good.

@thernstig
Copy link

@darrachequesne is this an easy fix?

@darrachequesne
Copy link
Member

This should be fixed by b7dd891, included in version 4.6.0.

Please reopen if needed!

@darrachequesne darrachequesne added this to the 4.6.0 milestone Feb 16, 2023
@sebamarynissen
Copy link

@darrachequesne I've noticed that the fix in b7dd891 causes another bug. If the socket uses multiplexing, the socket will emit two connection packets to the server. For example:

// Server receives the following connection packet *once*:
// 0/one
let one = io.connect('/one');

// Server receives the connection packet below *twice*:
// 0/two
let two = io.connect('/two');

This effectively creates two connected sockets to the /two namespace on the server. This leads to problems - which I encountered - when doing stuff on the server like

// Server
let nsp = io.of('/two');
nsp.on('connection', socket => {
  socket.join('room');
});

// Somewhere later
nsp.to('room').emit('foo', 'bar');

the two socket that was created on the client receives two foo events beause there are actually two connected sockets on the server, but they are the same on the client. I will see if I can create a test case and perhaps a fix for it next week.

The issue is quite critical for me because it means that chat messages in a chat system of mine get delivered twice.

@sebamarynissen
Copy link

I've been thinking a bit, and I think that the most elegant fix would be to only call socket.connect() again if

  1. the socket was cached
  2. the socket is in a disconnected state

So

let socket = this.nsps[nsp];
if (!socket) {
  socket = new Socket(this, nsp, opts);
  this.nsps[nsp] = socket;
} else if (socket.disconnected && this._autoConnect) {
  socket.connect();
}
return socket;

instead of how it's done now, being

let socket = this.nsps[nsp];
 if (!socket) {
  socket = new Socket(this, nsp, opts);
  this.nsps[nsp] = socket;
}
if (this._autoConnect) {
  socket.connect();
}
return socket;

darrachequesne added a commit that referenced this issue Feb 20, 2023
This bug was introduced in [1]: a multiplexed socket could in some
cases send multiple CONNECT packets, resulting in duplicate connections
on the server side.

A cached socket will now be reopened only if it was inactive, that is,
if one had explicitly called socket.disconnect() before.

Related: #1460

[1]: b7dd891
@darrachequesne
Copy link
Member

@sebamarynissen you are absolutely right, the fix did indeed contain a bug. This should be finally fixed by 46213a6, included in version 4.6.1. Could you please check?

@darrachequesne darrachequesne modified the milestones: 4.6.0, 4.6.1 Feb 20, 2023
@sebamarynissen
Copy link

@darrachequesne Checked and confirmed that 4.6.1 fixes the bug. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants