Pattern to connect 2 chat users to each other?

Let’s imagine we have the following collections

Users

  • id
  • nickname
  • inChat
  • createdAt

Chats

  • id
  • user1
  • user2

Now on the server we have a method, which is called by a user who want to chat with somebody:

connectChat: function () {





    Meteor.users.update(this.userId, {$unset:{'chatId':''}});

    var ownAccount = Meteor.users.findOne(this.userId);

    var finalUser = _.sample(Meteor.users.find({
        _id: {$ne: this.userId},
        'chatId': {$exists:false}
    }).fetch());

    //Okay, did we get a new user?

    if (finalUser) {


        var chat = Chats.findOne({
            $or: [{user1: this.userId, user2: finalUser._id}, {
                user1: finalUser._id,
                user2: this.userId
            }]
        });
        var chatId = null;
        if (chat) {
            chatId = chat._id;
        } else {
            chatId = Chats.insert({user1: this.userId, user2: finalUser._id, createdAt: new Date()});
        }


        Meteor.users.update(this.userId, {$set: {'chatId': chatId}});
        Meteor.users.update(finalUser._id, {$set: {'chatId': chatId}});


    }

}

This works fine, but has a big disadvantage: Sometimes, when multiple users call the function at the same time, they are joining chat rooms, where the other user isn’t in, because he also requested a chat and was connected to some other user.

How can I prevent such a behavior? What is the best way to handle such “same time requests”? My only idea for the moment is a cron job, which is running every 4 seconds and scans for users without a chat room - but this would be a problem if I scale my app to multiple instances.

2 ideas:

Immediately set a flag on the user so they can’t join another room whilst being connected to:

connectChat: function () {

  var user = Meteor.users.findOne(this.userId);

  if (user.connectedToChat) {
    return; // or throw error
  }

  var finalUser = _.sample(Meteor.users.find({
        _id: {$ne: this.userId},
        'chatId': {$exists: false}
    }).fetch());

  Meteor.users.update(finalUser._id, {$set: {connectedToChat: true}});

  try {
    Meteor.users.update(this.userId, {$unset:{'chatId':''}});
    // Rest of code
  }
  catch (error) {
    // Rollback flag just in case
    Meteor.users.update(finalUser._id, {$set: {connectedToChat: false}});
  }

or use something like a synced cron

Thanks, totally forgot that the percolate cronjobs fixes that problem :slight_smile: