Skip to content

Instantly share code, notes, and snippets.

@wankdanker
Created October 4, 2012 18:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wankdanker/3835374 to your computer and use it in GitHub Desktop.
Save wankdanker/3835374 to your computer and use it in GitHub Desktop.
uv_queue_work question
{
'targets' : [
{
'target_name' : 'modulename',
'sources' : [
'modulename.cpp'
]
}
]
}
#include <node.h>
#include <string>
using namespace v8;
// Forward declaration. Usually, you do this in a header file.
Handle<Value> Async(const Arguments& args);
void AsyncWork(uv_work_t* req);
void AsyncAfter(uv_work_t* req);
// We use a struct to store information about the asynchronous "work request".
struct Baton {
// libuv's request struct.
uv_work_t request;
// This handle holds the callback function we'll call after the work request
// has been completed in a threadpool thread. It's persistent so that V8
// doesn't garbage collect it away while our request waits to be processed.
// This means that we'll have to dispose of it later ourselves.
Persistent<Function> callback;
int32_t duration;
// Tracking errors that happened in the worker function. You can use any
// variables you want. E.g. in some cases, it might be useful to report
// an error number.
bool error;
std::string error_message;
// Custom data you can pass through.
int32_t result;
};
// This is the function called directly from JavaScript land. It creates a
// work request object and schedules it for execution.
Handle<Value> Async(const Arguments& args) {
HandleScope scope;
if (!args[0]->IsUint32()) {
return ThrowException(Exception::TypeError(
String::New("First argument must be an integer")));
}
if (!args[1]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("Second argument must be a callback function")));
}
//get the duration to sleep
int32_t duration = args[0]->ToUint32()->Value();
// There's no ToFunction(), use a Cast instead.
Local<Function> callback = Local<Function>::Cast(args[1]);
// This creates our work request, including the libuv struct.
Baton* baton = new Baton();
baton->error = false;
baton->request.data = baton;
baton->callback = Persistent<Function>::New(callback);
baton->duration = duration;
// Schedule our work request with libuv. Here you can specify the functions
// that should be executed in the threadpool and back in the main thread
// after the threadpool function completed.
int status = uv_queue_work(uv_default_loop(), &baton->request, AsyncWork, AsyncAfter);
assert(status == 0);
return Undefined();
}
// This function is executed in another thread at some point after it has been
// scheduled. IT MUST NOT USE ANY V8 FUNCTIONALITY. Otherwise your extension
// will crash randomly and you'll have a lot of fun debugging.
// If you want to use parameters passed into the original call, you have to
// convert them to PODs or some other fancy method.
void AsyncWork(uv_work_t* req) {
Baton* baton = static_cast<Baton*>(req->data);
printf("AsyncWork for duration: %i\n", baton->duration);
//sleep the requested duration
sleep(baton->duration);
// Do work in threadpool here.
baton->result = 42;
// If the work we do fails, set baton->error_message to the error string
// and baton->error to true.
}
// This function is executed in the main V8/JavaScript thread. That means it's
// safe to use V8 functions again. Don't forget the HandleScope!
void AsyncAfter(uv_work_t* req) {
HandleScope scope;
Baton* baton = static_cast<Baton*>(req->data);
printf("AsyncAfter for duration: %i\n", baton->duration);
if (baton->error) {
Local<Value> err = Exception::Error(String::New(baton->error_message.c_str()));
// Prepare the parameters for the callback function.
const unsigned argc = 1;
Local<Value> argv[argc] = { err };
// Wrap the callback function call in a TryCatch so that we can call
// node's FatalException afterwards. This makes it possible to catch
// the exception from JavaScript land using the
// process.on('uncaughtException') event.
TryCatch try_catch;
baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
} else {
// In case the operation succeeded, convention is to pass null as the
// first argument before the result arguments.
// In case you produced more complex data, this is the place to convert
// your plain C++ data structures into JavaScript/V8 data structures.
const unsigned argc = 2;
Local<Value> argv[argc] = {
Local<Value>::New(Null()),
Local<Value>::New(Integer::New(baton->result))
};
// Wrap the callback function call in a TryCatch so that we can call
// node's FatalException afterwards. This makes it possible to catch
// the exception from JavaScript land using the
// process.on('uncaughtException') event.
TryCatch try_catch;
baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}
// The callback is a permanent handle, so we have to dispose of it manually.
baton->callback.Dispose();
delete baton;
}
void RegisterModule(Handle<Object> target) {
target->Set(String::NewSymbol("async"),
FunctionTemplate::New(Async)->GetFunction());
}
NODE_MODULE(modulename, RegisterModule);
var modulename = require('./build/Release/modulename');
for (var x = 5; x >= 0; x-- ){
(function (x) {
console.warn('calling modulename.async(%s, ...);', x);
modulename.async(x, function(err, result) {
console.warn(x, result);
});
})(x);
}
Copy link

ghost commented Oct 28, 2013

Hi, nice example, thanks!

I am wondering what happens with the baton in case an exception was thrown either within AsyncWork. Moreover, I wonder if the baton is really deleted if in the callback of AsyncWorkDone an exception occurs. My initial tests show that the there my be a potential memory leak because in these cases the baton is never deleted. What are your thought on this?

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