2

I'm working on my project, which is based on socket.io room. I used socket with nodejs and manage room data in mongoDB.

Here is my code, Only two players can join a room and then after I turn IsGameOn flag false to true.
This code is working fine when I send request to server one by one.
Problem occurs when many request comes at a time. And problem is more than 2 players join the room (Room players' data store in aPlayers Array).

I upload an Image of the database also. So, you can see what happen actually in the database.

const joinRoom = async (sData, callback) => {

if(sData.iPlayerId && sData.eRoomCount)
{
    try {

        let body = _.pick(sData, ['eRoomCount', 'iPlayerId']);

        console.log(body);

        await roomsModel.aggregate([
            {
                $match: {
                    eRoomCount: body.eRoomCount,
                    IsGameOn: { $eq: false }
                }
            },
            { $unwind: "$aPlayers" },
            {
                $group: {
                    _id: "$_id",
                    eRoomCount: { $first: "$eRoomCount" },
                    aPlayers: { $push: "$aPlayers" },
                    size: { $sum: 1 }
                }
            },
            {
                $match: {
                    size: { '$lt': body.eRoomCount }
                }
            },
            { $sort: { size: -1 } }
        ]).exec((error, data) => {
            if (data.length < 1) {

                let params = {
                    eRoomCount: body.eRoomCount,
                    aPlayers: [{
                        iPlayerId: body.iPlayerId
                    }]
                }
                let newRoom = new roomsModel(params);
                console.log(JSON.stringify(newRoom));
                newRoom.save().then((room) => {
                    console.log("succ", room);
                    callback(null,room);
                }).catch((e) => {
                    callback(e,null);
                });
            } else {
                roomsModel.findOne({ _id: data[0]._id }, (error, room) => {

                    if (error) {
                        callback(error,null);
                    }

                    if (!room) {
                        console.log("No room found");
                        callback("No room found",null);
                    }

                    room.aPlayers.push({ iPlayerId: body.iPlayerId });
                    if (room.aPlayers.length === room.eRoomCount) {
                        room.IsGameOn = true;
                    }

                    room.save().then((room) => {
                        callback(null,room);
                    }).catch((e) => {
                        callback(e,null);
                    });

                })
            }
        });

    } catch (e) {
        console.log(`Error :: ${e}`);
        let err = `Error :: ${e}`;
        callback(e,null);
    }
    }
}

This is happens when request comes one by one. This is happens when request comes one by one.

This is happens when many request comes at a time. enter image description here

3
  • How do you access MongoDB? Are you using mongoose? Commented Jun 15, 2018 at 11:22
  • yes i'm using mongoose. Commented Jun 15, 2018 at 11:48
  • Sidenote, mixing promises,callbacks, and await/async like this is bound to cause maintenance confusion later. Commented Jun 18, 2018 at 8:02

2 Answers 2

2

The right way would be use mongoose's findOneAndUpdate instead of findOne. The operation findOneAndUpdate is atomic. If you do the right query, you can make your code thread safe.

// This makes sure, that only rooms with one or no player gets selected.
query = {
    // Like before
    _id: data[0]._id,
    // This is a bit inelegant and can be improved (but would work fast)
    $or: {
        { aPlayers: { $size: 0 } },
        { aPlayers: { $size: 1 } }
    }
}

// $addToSet only adds a value to a set if it not present.
// This prevents a user playing vs. him/herself
update = { $addToSet: { aPlayers: { iPlayerId: body.iPlayerId } } }

// This returns the updated document, not the old one
options = { new: true }

// Execute the query
// You can pass in a callback function
db.rooms.findOneAndUpdate(query, update, options, callback)
Sign up to request clarification or add additional context in comments.

Comments

0

for this problem you can use semaphore module, in your case Node Server accessing the concurrent request in a single thread environment but OS does the multitasking job. semaphore module will help you to overcome from this hell.

  • npm i semaphore            (install module)
  • const sem = require('semaphore')(1);    (require and specify concurrent request limit heres                      its 1 )
  • sem.take(function());            (wrap your function in it)
  • sem.leave();               (call it when your processing is done then the next request                   will access the function)

    const joinRoom = (sData, callback) => {
    
      sem.take(function () {
        if (sData.iPlayerId && sData.eRoomCount) {
    
          try {
    
            let body = _.pick(sData, ['eRoomCount', 'iPlayerId']);
            roomsModel.aggregate([
              {
                $match: {
                  eRoomCount: body.eRoomCount,
                  IsGameOn: { $eq: false }
                }
              },
              { $unwind: "$aPlayers" },
              {
                $group: {
                  _id: "$_id",
                  eRoomCount: { $first: "$eRoomCount" },
                  aPlayers: { $push: "$aPlayers" },
                  size: { $sum: 1 }
                }
              },
              {
                $match: {
                      size: { '$lt': body.eRoomCount }
                    }
                  },
                  { $sort: { size: -1 } }
                ]).exec((error, data) => {
                  if (data.length < 1) {
    
                    let params = {
                      eRoomCount: body.eRoomCount,
                      aPlayers: [{
                        iPlayerId: body.iPlayerId
                      }]
                    }
                    let newRoom = new roomsModel(params);
                    console.log(JSON.stringify(newRoom));
                    newRoom.save().then((room) => {
                      console.log("succ", room);
                      sem.leave();
                      callback(null, room);
                    }).catch((e) => {
                      sem.leave();
                      callback(e, null);
                    });
                  } else {
                    roomsModel.findOne({ _id: data[0]._id }, (error, room) => {
                      if (error) {
                        sem.leave();
                        callback(error, null);
                      }
                      if (!room) {
                        console.log("No room found");
                        sem.leave();
                        callback("No room found", null);
                      }
                      room.aPlayers.push({ iPlayerId: body.iPlayerId });
                      if (room.aPlayers.length === room.eRoomCount) {
                        room.IsGameOn = true;
                      }
                      room.save().then((room) => {
                        sem.leave();
                        callback(null, room);
                      }).catch((e) => {
                        sem.leave();
                        callback(e, null);
                      });
                    });
                  }
                });
              } catch (e) {
                console.log(`Error :: ${e}`);
                let err = `Error :: ${e}`;
                callback(e, null);
              }
            }
          });
        }
    

3 Comments

This adds a lot of boiler plate code, another dependency and reduces the performance of the app. It is better to ensure database consistency on the database level, which is possible with the right MongoDB function.
But sometimes we have to do multiple tasks on a selected data at that time it can be useful.
That might be. Concerning the question, this is not the case. In other, unnamed, hypothetical cases it is still questionable, if this solution is the best way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.