Skip to content

Instantly share code, notes, and snippets.

@idmillington
Created May 7, 2010 14:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save idmillington/393456 to your computer and use it in GitHub Desktop.
Save idmillington/393456 to your computer and use it in GitHub Desktop.
A function for returning a UUID (of any kind) in its canonical string form from within the node.js server-side javascript system. It uses a command-line OSSP uuid program to generate the UUIDs and caches them to avoid constantly spawning new processes.
/**
* Generates a new UUID and passes it to the given callback function.
*
* Uses the a command-line uuid generator. Caches UUIDs to avoid
* unneccessary spawning of new processes.
*
* You can easily adjust the script used to create the UUID. By
* default I am using the OSSP uuid program, which is available in
* most linux distro's package managers (e.g. `sudo apt-get install
* uuid` on ubuntu).
*
* Callback signature is function(err, uuid).
*
* Credits:
* Ian Millington
* Steve Brewer
*/
var createUUID = (function() {
// We create and execute this outside function immediately. Doing
// this provides us with a secure inner scope to store our uuid
// cache, preventing other code from accessing it. The result of
// this outmost function call is to return the actual get_uuid()
// function which is assigned to the createUUID name.
// Adjust these constants to tweak the uuid generation process.
// This version uses the OSSP uuid program, but you could also
// replace the script with your own uuidgen wrapper as per
// http://www.redleopard.com/2010/03/bash-uuid-generator/
var UUID_SCRIPT = "uuid";
var UUID_ARGS = ['-v', 4 /* uuid version */,
'-n', 100 /* per call */];
// Alias the spawn function for our cache top-up routine.
var spawn = require('child_process').spawn;
// The uuid stack. Pop UUIDs from this as they are needed.
var uuids = [];
// Track if we're currently generating, so we don't spawn new UUID
// generators if one is pending.
var spooledCallbacks = [];
var generating = false;
// Handle error notification of the spooled callbacks.
var notifyCallbacksOfError = function(error) {
while (spooledCallbacks.length > 0) {
spooledCallbacks.pop()(error, null);
}
}
// Updates the cache with another batch of UUIDs.
var topUpCache = function() {
// Create the shell call to uuid.
var uuidCall = spawn(UUID_SCRIPT, UUID_ARGS);
// When data arrives split it and cache it.
uuidCall.stdout.addListener('data', function(data) {
var result = data.toString();
// Strip whitespace from start and end of the data.
result = result.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
// Assume we got one UUID per line.
var uuidsReturned = result.split('\n');
uuids = uuids.concat(uuidsReturned);
});
// Pass errors up to the spooled callbacks, these are then
// popped, so they receive at most one error.
uuidCall.stderr.addListener('data', function(data) {
notifyCallbacksOfError(new Error("uuid generation error: "+data));
});
// If we're done, call the callback with the uuid.
uuidCall.addListener('exit', function(code) {
if (code != 0) {
notifyCallbacksOfError(new Error("uuid generation failed"));
} else {
// Send as many UUIDs as we can.
while(spooledCallbacks.length > 0 & uuids.length > 0) {
spooledCallbacks.pop()(null, uuids.pop());
}
if (spooledCallbacks.length > 0) {
// We didn't have enough uuids, so top up again.
topUpCache();
} else {
// We're done.
generating = false;
}
}
});
};
// Calls the given function with a UUID when one is calculated.
// Callback signature is function(err, uuid).
return function(callback) {
if (uuids.length > 0) {
// We can immediately send back a UUID.
callback(null, uuids.pop());
} else {
// We need to top up our cache before notifying the callback.
spooledCallbacks.unshift(callback);
// Check if we need to start a new cache update. If not,
// then just adding the callback to the spooled list will
// mean it gets called when UUIDs are available.
if (!generating) {
generating = true;
topUpCache();
}
}
};
})();
// Delete this line if you just want the function in your own code,
// add it if you want to use this file as a module and require() it
// into your program.
exports.createUUID = createUUID;
@idmillington
Copy link
Author

Using this function is as simple as:

get_uuid(function(err, uuid) {
    if (err) throw err;
    // do something with the uuid...
});

It should support any library for chaining deferred functions together.

@stevebrewer
Copy link

This can end up calling uuid multiple times - just call get_uuid while the generation command is running. I've modified it locally to queue up requests and make sure top_up_cache only runs once:

    if (uuids.length > 0) {
        callback(null, uuids.pop());
        return;
    } else{
        spooledCallbacks.push(callback);
        if(!generating){
            generating = true;
            // We need to top up our cache before returning.
            top_up_cache();
        }
    }

Success looks like this:

            while(spooledCallbacks.length > 0 && uuids.length > 0){
                spooledCallbacks.pop()(null, uuids.pop());
            }

            if(uuids.length <= 0){
                top_up_cache();
            } else {
                generating = false;
            }

I'm new to this gist, I'll see if I can figure out how to fork this...

@stevebrewer
Copy link

@idmillington
Copy link
Author

Thanks a lot Steve, yes, in my application UUID requests were rare so I didn't notice this obvious problem. Your solution works well.

Now I need to work out if I can pull your changes back here.

@idmillington
Copy link
Author

I've merged your changes in and done some additional work on tidying it up. I didn't pull your changes directly, because I wanted to keep the excessive comments in this bit of code, so GIST browsers can see what's going on.

@idmillington
Copy link
Author

Changed the callback registration to use unshift rather than push, so that we have a FIFO queue for pending uuids.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment