-
-
Save idmillington/393456 to your computer and use it in GitHub Desktop.
/** | |
* 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; |
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...
forked: http://gist.github.com/394936
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.
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.
Changed the callback registration to use unshift rather than push, so that we have a FIFO queue for pending uuids.
Using this function is as simple as:
It should support any library for chaining deferred functions together.