Search code examples
v8

v8::Locker exits current Context in Isolate


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();

Solution

  • 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.