Skip to content

.use() with async code causes timing issues with client and server connect events #2866

@sgress454

Description

@sgress454

You want to:

  • report a bug

Current behaviour

If .use() is used to add middleware to a Socket.io server, and the middleware contains asynchronous code, then a connecting client may receive a connect event from the server before the server triggers its own connect event. This can cause some race conditions where, for example, the client emits an event in its connect handler, but the server hasn't bound any event handlers yet.

Steps to reproduce

Create a file test.js with:

// Create a new socket.io server, websocket-only.
var server = require('http').createServer();
var io = require('socket.io')(server, {wsEngine: process.argv[3] || 'uws', transports: ['websocket']});

// Define a delay to use in the middleware.
var DELAY_MS = parseInt(process.argv[2] || 50);

// Add some middleware.
io.use(function(socket, next){
    // If DELAY_MS is set to zero, return synchronously.
    if (DELAY_MS === 0) {return next();}
    // Otherwise do a couple of setTimeouts to run the event loop for a bit.
    setTimeout(function() {
      setTimeout(next, DELAY_MS);
    }, DELAY_MS);
});

// When a socket connects to the server, add an 'event' handler to the socket.
io.on('connect', function(client){
  console.log('Server got socket connection!');
  client.on('event', function(data, cb){
    console.log('Server received event.');
    process.exit(0);
  });
});

// Start the server.
server.listen(3000);

// Connect a client socket using socket.io-client, websocket-only.
var socket = require('socket.io-client')('http://localhost:3000', {transports: ['websocket']});

// When the client socket gets the "connect" event from the server, send an "event" message.
socket.on('connect', function(){
  console.log('Client got socket connection!  Sending event...');
  socket.emit('event');
});

and run with node test.js to see the undesirable behavior. Run with node test.js 0 to see the expected behavior.

Expected behaviour

Ideally, the server should receive its connect event before the client, so the console logs would be:

Server got socket connection!
Client got socket connection!  Sending event...
Server received event.

That's what you get if you run node test.js 0, which causes the middleware to return synchronously.

Using the default settings, node test.js instead yields:

Client got socket connection!  Sending event...
Server got socket connection!

and hangs.

Stranger yet, if you do node test.js 1 ws, which causes the middleware to run a couple of setTimeouts with 1 ms delays, using the older ws WebSocket engine, you sometimes get:

Client got socket connection!  Sending event...
Server got socket connection!
Server received event.

because the packet being sent from the client doesn't arrive until after the server connect handler runs. This effect is more pronounced with the ws WebSocket engine than with uws. Run the tests with DEBUG=* to see all the gory details.

In any case, it seems like the connect event should never be sent to the client before the connect event handler on the server is called!

Setup

  • OS: OS X 10.11.1
  • browser: n/a
  • socket.io version: 1.7.2
  • socket.io-client version: 1.7.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions