I'm trying to embed V8 in a multi-threaded application. My plan was to have the V8 code executed inline, in whatever thread currently needs to execute JS code.
Therefore, in my execution context I'm acquiring a v8::Locker
and entering the Isolate in a random thread. I notice that when acquiring the v8::Locker
, the Isolate loses it's CurrentContext.
In the code snippet below I boiled it down to the essence of the problem. If I Enter
a context under a v8::Locker, exit that locker and enter a new one, the Isolate loses its Context. Notice that I never explictly Exit
the Context.
Is this a correct usage? Am I missing something?
In the code snippet below, you can see a single-threaded dummy example of the problem. Asserts 1 and 2 pass and assert 3 fails.
v8::V8::InitializeICU(V8_ICU_FILE);
v8::V8::InitializeExternalStartupData(V8_SNAPSHOT_BIN_FILE);
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
// Create a new Isolate and make it the current one.
v8::Isolate::CreateParams create_params;
auto allocator = std::unique_ptr<v8::ArrayBuffer::Allocator>(v8::ArrayBuffer::Allocator::NewDefaultAllocator());
create_params.array_buffer_allocator = allocator.get();
v8::Isolate* isolate = v8::Isolate::New(create_params);
v8::HandleScope handle_scope(isolate);
// Create a new context.
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
global->Set(isolate, "log", v8::FunctionTemplate::New(isolate, logCallback));
v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
// Enter the context for compiling and running the hello world script.
{
v8::Locker locker(isolate);
v8::Isolate::Scope scope(isolate);
context->Enter(); // Explicitly Enter the context (Which makes it the CurrentContext)
assert(!isolate->GetCurrentContext().IsEmpty()); // 1 - Succeeds
isolate->Exit();
isolate->Enter();
assert(!isolate->GetCurrentContext().IsEmpty()); // 2 - Succeeds
}
{
v8::Locker locker(isolate);
v8::Isolate::Scope scope(isolate);
assert(!isolate->GetCurrentContext().IsEmpty()); // 3 - Assertion Fails
}
// Create a string containing the JavaScript source code.
isolate->Dispose();
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
The v8::Locker
destructor assumes that the current thread is done with whatever it was doing and cleans up afterwards; in particular that means that it unsets the current context. You'll have to (re-)enter the correct context after creating the next Locker on the next thread. v8::Context::Scope
makes this convenient.