Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meteor: Could a race condition happen with Meteor.collections on server side?

in my server/server.js

Meteor.methods({
    saveOnServer: function() {
        var totalCount = Collections.find({
            "some": "condition"
        }).count();
        if (totalCount) {
            var customerId = Collections.update('someId', {
                "$addToSet": {
                    objects: object
                }
            }, function(err) {
                if (err) {
                    throw err;
                } else {
                    return true;
                }
            });
        } else {}
    }
});

I'm afraid that when saveOnServer() is called by 2 clients at the same time, it will return the same totalCount for each client and basically end up inserting same integer number into object id. The end goal is to insert row on the server side with an atomic operation that only completes when the totalCount is successfully returned and the document is inserted ensuring that no duplicate id exists? I'm trying to not use the mongodb _id but have my own integer incrementing id column.

I'm wondering how I can ensure that a field gets auto-incremented for each insert operation? I am currently relying on getting the total count of documents. Is a race condition possible here? If so, what is the meteor way of dealing with this?

like image 784
KJW Avatar asked Aug 02 '13 00:08

KJW


2 Answers

In Meteor's concurrency model, you can imagine a whole method as an uninterruptible block of stuff that happens. In order for Meteor to switch from running one method midway to say, starting another method, you need to "yield"—the method needs to signal, "I can be interrupted."

Methods yield whenever they do something asynchronous, which in practice means any time you do a database update or call a method with a callback in Meteor 0.6.5 and later. Since you give your update call a callback, Meteor will always try to do something in between the call to update and the update's callback. However, in Meteor 0.6.4.2 and earlier, database updates were uninterruptible regardless of the use of callbacks.

However, multiple calls to saveOnServer will happen in order and do not cause a race condition. You can call this.unblock() to allow multiple calls to saveOnServer to occur "simultaneously"—i.e., not share the same queue, labeled saveOnServer queue, of uninterruptible blocks of stuff.

Given the code you have, another method modifying Collections can change the value of count() between the call and the update.

You can prevent one method from making the other invalid midway by implementing the following data models:

saveOnServer : function () {
// ...
  Collections.update({_id:someId, initialized:true, collectionCount: {$gt: 0}},
    {$addToSet: {objects: object}});
///...
}

When adding objects to Collections:

insertObject: function() {
//...
  var count = Collections.find({some: condition}).count();
  Collections.insert({_id:someId, initialized:false, collectionCount: count});
  Collections.update({initialized:false},
    {$set:{initialized:true}, $inc: {collectionCount: 1}});
}

Note, while this may seem inefficient, it reflects the exact cost of making an update and insert in different methods behave the way you intend. In saveOnServer you cannot insert.

Conversely, if you removed the callback from Collections.update, it will occur synchronously and there will be no race conditioning Meteor 0.6.5 and later.

like image 110
DoctorPangloss Avatar answered Nov 09 '22 03:11

DoctorPangloss


You can make this collection have a unique key on an index field, and then keep it updated as follows:

1) Whenever you insert into the collection, first do a query to get the maximum index and insert the document with index + 1.

2) To find out the number of documents just do the query to get the max of the index.

Insertion is now a pair of queries, a read and a write, so it can fail. (DB ops can always fail, though.) However, it can never leave the database in an inconsistent state - the Mongo index will guarantee that.

The syntax for building an index in Meteor is this:

MyCollection._ensureIndex('index', {unique: 1});
like image 1
disatisfieddinosaur Avatar answered Nov 09 '22 02:11

disatisfieddinosaur