Skip to content

Packets getting delivered to wrong room #2962

@gkcgautam

Description

@gkcgautam

You want to:

  • report a bug
  • request a feature

Bug

We use Socket.io for running a Chat service wherein students can chat with teachers for getting their doubts solved. We've been getting reports of messages being delivered to a wrong student or sometimes messages getting lost before they reach the student. Based on the investigation that we did, it appears that if a single socket instance (teacher) emits messages to several rooms simultaneously, some of the messages might make it to a wrong room.
This might also be related to #2726

Current behaviour

This happens because when invoking socket.to('room') or socket.in('room'), the list of target rooms is stored within the _rooms key of the socket instance with this code:

Socket.prototype.to =
Socket.prototype.in = function(name){
  if (!~this._rooms.indexOf(name)) this._rooms.push(name);
  return this;
};

When we call the socket.emit method, after setting the target room, it checks whether the message is intended for a room with this code:

if (this._rooms.length || this.flags.broadcast) {
  this.adapter.broadcast(packet, {
    except: [this.id],
    rooms: this._rooms,
    flags: this.flags
  });
} else {
  // dispatch packet
  this.packet(packet, this.flags);
}

If this._rooms is not empty, the message gets broadcasted to the rooms present in the array.
While this approach works fine in a synchronous code, or on a smaller scale, it can create problems if the same socket instance tries to broadcast large number of different messages to different rooms simultaneously.

Just to give an example, let's assume that a single socket instance is going to emit messages to multiple rooms simultaneously from two different async functions:

// Message 1 to Room 1 in an async function
socket.to('room-1').emit('nice game', 'well done');
...
// Message 2 to Room 2 in another async function
// by the same socket instance
socket.to('room-2').emit('poor game', 'we need to do better next time');

Now if the socket.to('room-2') code was to get invoked immediately after socket.to('room-1'), the message intended for room-1 will also be sent to room-2 as both of the rooms will be present in the socket._rooms array when the emit method is invoked for first message.

The order in which the methods actually got invoked:

// _rooms is []
socket.to('room-1')
// _rooms is ['room-1'] now
socket.to('room-2')
// _rooms is ['room-1', 'room-2'] now
emit('nice game', 'well done');
// The above message will be sent to both rooms

Temporary fix

As a temporary fix, we've forked the socket.io repo and created a new emitToRoom method wherein we can pass the room names and messages within a single invocation and do not need to use the socket._rooms key. This is our fix:

Socket.prototype.emitToRoom = function(){
  var args = Array.prototype.slice.call(arguments);
  if (args.length < 2) {
    throw new Error('Insufficient arguments provided');
  }

  var rooms = args.shift();
  if (!Array.isArray(rooms)) {
    rooms = [rooms];
  }

  var packet = {};
  packet.type = hasBin(args) ? parser.BINARY_EVENT : parser.EVENT;
  packet.data = args;

  // access last argument to see if it's an ACK callback
  if ('function' == typeof args[args.length - 1]) {
    throw new Error('Callbacks are not supported when broadcasting');
  }

  this.adapter.broadcast(packet, {
    except: [this.id],
    rooms: rooms,
    flags: {}
  });
  return this;
};

Setup

  • OS: N/A
  • browser: N/A
  • socket.io version: All versions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions