Search code examples
phpmongodboauthlockingblocking

MongoDB `GET_LOCK` Solution in PHP


I am trying to find a solution to prevent race conditions in my application logic (specifically when renewing an OAuth access token) and my back-end database happens to be mongodb.

Coming from a MySQL background, I'm used to using GET_LOCK and it's related functions to handle blocking in PHP. Does Mongo have any analog to MySQL's GET_LOCK function, or will I have to use PHP's file locking or something similar?

Is flock() a good (or proper) alternative for this situation, or is that meant only for use when reading and writing to files?

Edit:

The race condition I am trying to prevent is the following:

  1. Instance A notices OAuth access token nearing expiration

  2. Instance B notices OAuth access token nearing expiration

  3. Instance A requests refreshed OAuth access token from remote server and obtains one

  4. Instance B requests refreshed OAuth access token from the same server and is rejected (server potentially invalidates access token from step 3 as security precaution)

  5. Instance A saves result back to database

  6. Instance B saves result back to database


Solution

  • If you want to simulate a named mutex or lock using MongoDB, I would suggest using findAndModify by creating a special collection for it and having a document, you can even call it db.my_lock.

    db.my_lock.save({"IMREFRESHINGAUTHTOKEN":false});
    

    Now, between steps 2 and 3 add a findAndModify to grab the "lock":

    db.my_lock.findAndModify(
            query: {"IMREFRESHINGAUTHTOKEN":false},
            update: {$set: {"IMREFRESHINGAUTHTOKEN": true}, ...}
    );
    

    If you get to the "lock" first, you will get back this object (and you will get to set the first field to true - I recommend setting a second field with timestamp or connection number or process ID or some other identifier which will allow cleaning up after a crashed process so it won't hold a lock forever).

    If you "lose" the race you will get back nothing that matches "IMREFRESHINGAUTHTOKEN":false and you'll know you lost the race and give up (or check the timestamp of the lock and maybe try to see if it's old and stale).

    This describes a stand-alone single lock on the whole "collection" - of course you can implement this as an extra field on the stored OAuth token and have as many of them being refreshed at a time as there are threads noticing they are expiring.

    Hope this helps.