Howardism Musings from my Awakening Dementia
My collected thoughts flamed by hubris
Home PageSend Comment

Locking in Node.js

Ran into a odd situation with our Node.js project. Normally, when you request access to a database (in our case, we're using MongoDB), you give it a callback, which is executed when the connection pool has been initialized and is ready for access.

mongodb.connect( dburl, options, function( err, conn ) {
    if ( err ) {
        // Uh, barf?
    }
    else {
        // Do some work, like:
        // conn.find(..);
    }
});

Of course, calling connect() for each HTTP request is actually pretty bad since connect() is calling out to a pool. A better approach is to wrap up your HTTP server in the callback.

mongodb.connect( dburl, options, function( err, conn ) {
    if ( err ) {
        console.warn("Can't connect to MongoDB", err);
    }
    else {
        app.listen(config.port, config.host);
    }
});

This works fine on our HTTP server, however, this project is cloud-based, meaning we have many micro services running, and a couple of them didn't have a single start function like the HTTP gateway server.

One of my engineers asked if we couldn't make create a getConnection() function in our database interface module that any function could call.

Good idea, but the problem with the Mongo-Node driver is that the first request starts up the pool, and it currently can't handle multiple requests until the pool is setup.

Ah, a challenge.

What we need is a function that:

  • Allow the first request access to the database
  • Block all other database access requests
  • Once the first request is done, release the other requests

I created a lock.js module that my database interface module would require.

The module has a lock.get() that return true if the lock is free, false otherwise. This function takes a callback, which is executed once the lock is free. My database module's getConnection() function looked like:

var getConnection = function( callback ) {

    if( lock.get( callback ) ) {
        mongodb.connect( dburl, dboptions, function( err, conn ) {
            if( err ) {
                callback( err );
            }
            else {
                callback( null, conn );
                lock.free( this, [ null, conn ] );
            }
        });
    } // end if lock.get

The lock.free() function allows all of the previously blocked functions to run, by calling .apply() on it. That is why we pass in a this variable and an array of parameters (a null of the error, and the conn database connection variable).

The code for lock.js starts with a couple of module-level variables: a variable to lock, and an array of callbacks of all the blocked requesters:

/**
 * Default locked variable, set it to `true` until free
 */
var locked = false;

var requesters = [];

The getLock() is pretty simple, since in JavaScript each function is autonomous (until a function is called):

/**
 * Return current lock status. If it's locked, put the callback into
 * the `requesters` array to be called later.
 *
 * @param {function} callback  run once lock is free.
 */
var getLock = function( cb ) {
    if( locked ) {
        requesters.push( cb );
        return false;
    }
    else {
        locked = true;
        return true;
    }
}; // end var getLock
exports.get = getLock;

This function sets the locked variable to true, if it is not locked, and if it is, pushes the callback on the array.

The freeLock() function does an .apply() on each callback, as in:

/**
 * Free lock status. Also run the all the methods in `requesters`
 * array using the given environment specified by the `that`
 *
 * @param {Object} that running environment
 * @param {Array} params arguments passed to `requester`.
 */
var freeLock = function( that, params ) {
    locked = false;
    for ( var r = 0; r < requesters.length; r++ ) {
        requesters[r].apply( that, params );
    } // end for
}; // end var freeLock
exports.free = freeLock;
Tell others about this article:
Click here to submit this page to Stumble It