Are ordinary functions blocking in meteor?

Are ordinary functions blocking in meteor? Consider the function below. If two users access at around the same time, I get

E11000 duplicate key error collection: srs-3_meteor_com.users index: username_1 dup key

That led me to think that, both users checked Meteor.users.findOne at same time, both returning undefined/null, thus user not existing yet. Then both go through and have a race for Meteor.users.insert, in which case, an error is going to occur, whoever is first. Probably, the fibre only guarantees blocking for db ops.

function insert_or_update_moodle_user(login_request) {
check(login_request.lms_id, String);
check(login_request.lms_role, String);
check(login_request.lms_course_id, String);

let username = `moodle-${login_request.lms_id}`;
let user = Meteor.users.findOne({username: username, 'profile.type': 'moodle'});

console.log(username);

// todo: if someohow moodle-<username> was used already, append an incrementor on it based on number of duplicates.

/*
 * If user does not exist, create that user. Otherwise,
 * update the user's current course and lms_role.
 */
if (!user) {
    Meteor.users.insert({
        username: username,
        password: Meteor.uuid(),  // Generate random password. Not to be used for logging, but Meteor requires this.

        // LMS specific data.
        profile: {
            type: 'moodle',
            lms_id: login_request.lms_id,
            lms_role: login_request.lms_role,
            lms_current_course: login_request.lms_course_id
        }
    });

    console.log(username + ' inserted');
} else {
    Meteor.users.update({
        username: username,
        'profile.type': 'moodle'
    }, {
        $set: {
            'profile.lms_current_course': login_request.lms_course_id,
            'profile.lms_role': login_request.lms_role
        }
    });
}

// Get the created/updated user.
return Meteor.users.findOne({username: username, 'profile.type': 'moodle'});

}

Nothing is blocking in Meteor. Fibers are actually a way of making synchronous code non-blocking. Methods from two different clients can easily execute in parallel.

So how can I “place mutex” around it? Will wrapping in Meteor.methods solve it? I’m confused.

This is a very similar thread, I’d suggest reading it:

Basically, since you’re going to be running multiple servers of your application and MongoDB doesn’t have transactions, it’s hard to guarantee a lock, but it can be done with some clever programming.

1 Like

My code is running in srs-3.meteor.com, so that is multiple servers? One more thing, that function is not being executed via ddp call, but rather via server side route from another web app. After creating/updating user, I then redirect the client, where there I do loginWithToken.

Nevermind, I got it. I was just staring at the method in meteor/livedata_server.js line 627, in which the transaction was wrapped in DDPServer._WriteFence. For the life of me, I was searching in github on the point of this code, I can’t find it. I asked the question if wrapping in in Meteor.methods/call, cause I thought this code was related to that, but it seems like its not.

On the other hand, Meteor.call is not wrapped by this DDPServer._WriteFence at all, which seems like a shame.

Anywayz, I’ve looked at account-pasword, and in there, discovered that Account.insertUserDoc (or something), wrap the insert in a try/catch, proving further that interlacing between these can occur.

In conclusion, I have stolen the CREATE USER section in account-password package.


Edit: After reading further:

DDPServer._WriteFence is established inside Session.method call, in which is created for every user transaction. Thus,

(Client) Method.call =>
(Server) new Session => This creates a queue of messages (methods), wrapped in Fiber. =>
Each execution of messages are establish DDPServer._WriteFence, also setting Meteor.Environment DDPServer._WriteFence instance =>
Any transaction to mongo db transaction check if this Meteor.Environment is set, if so, DDPServer._WriteFence’s will be utilized to lock the transaction.

In my NEW CONCLUSION, Meteor.call from client is guaranteed to be race free. Server on the other hand just directly calls Server.call (which I assume is the same as Meteor.call). Since no DDPServer._WriteFence in that code, race condition could occur.

1 Like