I'm writing a Node plugin and I'm having problems trying to call a V8 function object from a C++ worker thread.
My plugin basically starts a C++ std::thread and enters a wait loop using WaitForSingleOject(), this is triggered by a different C++ app (An X-Plane plugin) writing to a bit of shared memory. I'm trying to get my Node plugin to wake up when the Windows shared event is signaled then call a JavaScript function that I've registered from the node app, which will in turn pass the data which originated in X-Plane back to Node and the web world.
I've managed to work out how to register a JavaScript function and call it from C++, but only in the main V8 thread. I can't seem to find a way of calling the function from the std::thread.
I've tried various approaches, Locker objects (variable success), Persistent functions (didn't work), saving the main isolate object, entering/exiting the isolate, but if/when the code eventually reaches the function object it's not valid.
I get different results, ranging from crashing to freezing depending on whether I create various locker and unlocker objects.
I'm totally new to V8, so I'm not really sure I'm doing anything right. The code in question as follows:
If anyone could help at all I'll be eternally grateful!.
float* mem = 0;
HANDLE event = NULL;
Isolate* thisIsolate;
void readSharedMemory()
{
//Isolate* isolate = Isolate::GetCurrent();
//HandleScope scope(isolate);
thisIsolate->Enter();
v8::Locker locker(thisIsolate);
v8::Isolate::Scope isolateScope(thisIsolate);
//HandleScope scope(thisIsolate);
//v8::Local<Value> myVal = v8::String::NewFromUtf8(isolate, "Plugin world");
v8::Local<Value> myVal = v8::Number::New(thisIsolate, *mem);
// If it get's this far 'myFunction' is not valid
bool isFun = myFunction->IsFunction();
isFun = callbackFunction->IsFunction();
v8::Context *thisContext = *(thisIsolate->GetCurrentContext());
myFunction->Call(thisContext->Global(), 1, &(Handle<Value>(myVal)));
}
void registerCallback(const FunctionCallbackInfo<Value>& args)
{
Isolate* isolate = Isolate::GetCurrent();
v8::Locker locker(isolate);
HandleScope scope(isolate);
/** Standard parameter checking code removed **/
// Various attempts at saving a function object
v8::Local<v8::Value> func = args[0];
bool isFun = func->IsFunction();
Handle<Object> callbackObject = args[0]->ToObject();
callbackFunction = Handle<Function>::Cast(callbackObject);
isFun = callbackFunction->IsFunction();
// save the function call object - This appears to work
myFunction = v8::Function::Cast(*callbackObject);
isFun = myFunction->IsFunction();
// Test the function - this works *without* the Unlocker object below
v8::Local<Value> myVal = v8::String::NewFromUtf8(isolate, "Plugin world");
myFunction->Call(isolate->GetCurrentContext()->Global(), 1, &(Handle<Value>(myVal)));
}
void threadFunc()
{
thisIsolate->Exit();
// If I include this unlocker, the function call test above fails.
// If I don't include it, the app hangs trying to create the locker in 'readSharedMemory()'
//v8::Unlocker unlocker(thisIsolate);
event = OpenEventW(EVENT_ALL_ACCESS, FALSE, L"Global\\myEventObject");
DWORD err = GetLastError();
//thisIsolate = v8::Isolate::New();
std::cout << "Hello from thread" << std::endl;
bool runThread = true;
while (runThread)
{
DWORD dwWaitResult;
DWORD waitTime = 60000;
dwWaitResult = WaitForSingleObject(event, waitTime);
err = GetLastError();
if (dwWaitResult == WAIT_TIMEOUT)
runThread = false;
// event has been signaled - continue
readSharedMemory();
}
}
void init(Handle<Object> exports)
{
/** NODE INITILISATION STUFF REMOVED **/
// save the isolate - Is this a safe thing to do?
thisIsolate = Isolate::GetCurrent();
//Launch a thread
eventThread = std::thread(threadFunc);
}
You probably need a bit of
libuv
magic to get the main node.js/v8 thread to execute your callback from another thread. This will involve:A uv_async_t handle which acts as the wake-up call for the main v8 thread:
A
uv_async_init
call which binds the uv_async_t to the V8 default loop:And a event handler which will act on the uvasync_t event on the v8 main thread:
Finally, you'll also probably need a mutex-protected queue to be able to pass some data from the c++ addon thread to Node/V8:
When something happens in your C++ thread, just push a new item to the mutex-protected queue, and call
uv_async_send
to wake up V8's default event loop (the so called 'main thread') to process the item (which can then call your Javascript callbacks)(Code snippets taken from the official Node.JS addon for OpenZWave)