Crash during v8::Isolate::New() with overlapping isolate lifetimes

41 views
Skip to first unread message

Sae-Won

unread,
Jun 30, 2026, 9:05:12 AM (24 hours ago) Jun 30
to v8-users
Hello everyone,

I am investigating what looks like a possible issue during V8 initialization / isolate creation, and I would like to know whether I am missing an embedder requirement or whether this could be a V8 issue.

I have a very small reproducer which creates and destroys isolates from multiple threads.

The reproducer does the following:

1. Initializes V8 (`InitializePlatform()` / `Initialize()`).
2. Creates two isolates from two different threads.
3. Each thread creates an isolate, waits briefly, then disposes it.

Observations:

* The issue only happens if the second `v8::Isolate::New()` starts while the first isolate is still alive.
* If I fully serialize `Isolate::New()` / `Dispose()` calls together, the crash disappears.
* If V8 initialization is performed from the main thread, I cannot reproduce the crash anymore.

However, in my actual embedding scenario I cannot guarantee that V8 initialization happens from the main thread, so this is not a practical workaround for my use case.

Environment:

* Linux x86_64
* V8 13.7.152.19 and V8 15.0.245.13
* Built with GN args:

target_os="linux"
target_cpu="x64"
is_debug=false
v8_monolithic=true
v8_monolithic_for_shared_library=true
is_component_build=false
v8_use_external_startup_data=false
v8_enable_i18n_support=true
icu_use_data_file=true
use_custom_libcxx=false
use_custom_libcxx_for_host=false
use_sysroot=false
treat_warnings_as_errors=false
is_clang=true
v8_enable_sandbox=false

The crash consistently occurs during the second `v8::Isolate::New()`:

v8::internal::JSDispatchTable::PreAllocatedEntryNeedsInitialization(...)
v8::internal::Isolate::InitializeBuiltinJSDispatchTable()
v8::internal::Isolate::Init(...)
v8::internal::Snapshot::Initialize(...)
v8::Isolate::Initialize(...)
v8::Isolate::New(...)

The exact same executable does not reproduce on RHEL 8 / Rocky Linux 8, but reproduces on RHEL 9 / Rocky Linux 9.

I have reproduced it on multiple RHEL 9 systems, while I have not been able to reproduce it on RHEL 8 systems.

Things already tested:

* V8 13.7.152.19 and V8 15.0.245.13
* GCC and Clang builds
* Release and DCHECK builds
* ASan/TSan
* V8 sandbox enabled/disabled
* serializing isolate creation and destruction with a mutex
* keeping the thread that initializes V8 alive for the lifetime of the process

Are there any restrictions regarding:

* the thread performing the initial V8 initialization?
* creating multiple isolates with overlapping lifetimes?
* any required synchronization around isolate or global V8 initialization?

A minimal reproducer is available below.

Thanks!

_____________________________________________________________________________

#include <thread>
#include <vector>
#include <memory>
#include <mutex>
#include <chrono>
#include <iostream>

#include "v8.h"
#include "libplatform/libplatform.h"

std::once_flag platformFlag;
std::unique_ptr<v8::Platform> platform;
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator;
std::mutex isolateMutex;

using namespace std::chrono_literals;

void CreateAndDestroyIsolate()
{
    isolateMutex.lock();
    v8::Isolate::CreateParams params;
    params.array_buffer_allocator_shared = allocator;
    v8::Isolate* isolate = v8::Isolate::New(params);
    isolateMutex.unlock();

    std::this_thread::sleep_for(100ms);

    isolateMutex.lock();
    isolate->Dispose();
    isolateMutex.unlock();
}

void InitV8()
{
    std::call_once(platformFlag, []
    {
        platform = v8::platform::NewDefaultPlatform();
        v8::V8::InitializePlatform(platform.get());
        v8::V8::Initialize();
        allocator.reset(v8::ArrayBuffer::Allocator::NewDefaultAllocator());
    });
}

int main()
{
    std::thread init_thread([] {
        InitV8();
    });

    init_thread.join();

    std::vector<std::thread> threads;

    for (int i = 0; i < 2; i++) {
        threads.emplace_back(CreateAndDestroyIsolate);
    }

    for (auto& t : threads)
        t.join();
 
    return 0;
}

Omer Katz (chromium.org)

unread,
Jun 30, 2026, 11:38:09 AM (21 hours ago) Jun 30
to v8-u...@googlegroups.com
Hello Sae-Won,

In general V8 assumes a main isolate that is initialized before all other isolates and also outlives them.
Perhaps your issue is caused by the first isolate being destroyed either before or during the initialization of the second isolate?
I haven't tried to reproduce your issue so I don't know if that's it.

The stack trace you pasted includes "v8::internal::JSDispatchTable::PreAllocatedEntryNeedsInitialization" which doesn't exist in the code anymore.
If the initialization/deinitialization order is not the issue, can you try reproducing it on the latest version of V8?

Omer

--
--
v8-users mailing list
v8-u...@googlegroups.com
http://groups.google.com/group/v8-users
---
You received this message because you are subscribed to the Google Groups "v8-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to v8-users+u...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/v8-users/5c14f3f1-7219-4bc6-9d83-bd5649d1da2an%40googlegroups.com.

Sae-Won

unread,
6:20 AM (2 hours ago) 6:20 AM
to v8-users
Hello Omer,

Thank you for your quick reply.

In my scenario, the dispose of the v8::Isolate is never called, the crash occurs before :

Thread 1 begins
v8::platform::NewDefaultPlatform (from thread 1)
v8::V8::InitializePlatform  (from thread 1)
v8::V8::Initialize()  (from thread 1)
Thread 1 ends

Thread 2 and Thread 3 begin
Thread 2 locks the mutex and Thread 3 is blocked by the mutex
v8::Isolate::New (from thread 2)
Thread 2 unlocks the mutex and sleeps for 100ms
v8::Isolate::New (from thread 3) ==> Crash

About "v8::internal::JSDispatchTable::PreAllocatedEntryNeedsInitialization":

The call stack was with the last stable release of V8 : 15.0.245.13
I tried with the last version I found 15.1.206.1, it still crashes with the same stack:
#0  0x0000000001066bcd v8::internal::JSDispatchTable::PreAllocatedEntryNeedsInitialization(v8::internal::ExternalEntityTable<v8::internal::JSDispatchEntry, 268435456ul>::Space*, v8::base::StrongAlias<v8::internal::JSDispatchHandleAliasTag, unsigned int>) (v8_isolate_repro_151 + 0xe66bcd)
#1  0x0000000000a5dff0 v8::internal::Isolate::InitializeBuiltinJSDispatchTable() (v8_isolate_repro_151 + 0x85dff0)
#2  0x0000000000a5684a v8::internal::Isolate::Init(v8::internal::SnapshotData*, v8::internal::SnapshotData*, v8::internal::SnapshotData*, bool) (v8_isolate_repro_151 + 0x85684a)
#3  0x0000000000a57079 v8::internal::Isolate::InitWithSnapshot(v8::internal::SnapshotData*, v8::internal::SnapshotData*, v8::internal::SnapshotData*, bool) (v8_isolate_repro_151 + 0x857079)
#4  0x0000000001085fda v8::internal::Snapshot::Initialize(v8::internal::Isolate*) (v8_isolate_repro_151 + 0xe85fda)
#5  0x00000000008eb8b9 v8::Isolate::Initialize(v8::Isolate*, v8::Isolate::CreateParams const&) (v8_isolate_repro_151 + 0x6eb8b9)
#6  0x00000000008ebb6b v8::Isolate::New(v8::Isolate::CreateParams const&) (v8_isolate_repro_151 + 0x6ebb6b)
#7  0x00000000008aa13c CreateAndDestroyIsolate() (v8_isolate_repro_151 + 0x6aa13c)
#8  0x00000000008ab6b2 void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (v8_isolate_repro_151 + 0x6ab6b2)
#9  0x00000000008ab675 std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (v8_isolate_repro_151 + 0x6ab675)
#10 0x00000000008ab64d void std::thread::_Invoker<std::tuple<void (*)()>>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (v8_isolate_repro_151 + 0x6ab64d)
#11 0x00000000008ab625 std::thread::_Invoker<std::tuple<void (*)()>>::operator()() (v8_isolate_repro_151 + 0x6ab625)
#12 0x00000000008ab589 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()>>>::_M_run() (v8_isolate_repro_151 + 0x6ab589)
#13 0x00007fdbfb8dbae4 execute_native_thread_routine (libstdc++.so.6 + 0xdbae4)
#14 0x00007fdbfb48b3f9 start_thread (libc.so.6 + 0x8b3f9)
#15 0x00007fdbfb5105d0 __clone3 (libc.so.6 + 0x1105d0)

In which version the API "v8::internal::JSDispatchTable::PreAllocatedEntryNeedsInitialization" gets removed ?


It seems that the crash could be related to PKU, because it crashes on RHEL9 with PKU but doesn't crash on RHEL9 without PKU.

So I did some further investigation using GDB and strace.

Under GDB, at the crash point:
(gdb) p $_siginfo.si_code
$1 = 4

I checked the Linux headers:
$ grep -R "SEGV_PKUERR" /usr/include
/usr/include/asm-generic/siginfo.h:# define SEGV_PKUERR 4 /* failed protection key checks */

So the kernel reports this as a protection key violation (SEGV_PKUERR)

I also traced the system calls:

$ strace -f ./v8_isolate_repro_151 2>&1 | grep pkey
[pid 865176] pkey_alloc(0, PKEY_DISABLE_WRITE) = 1
[pid 865176] pkey_mprotect(0x2627000, 4096, PROT_READ, 0) = 0
[pid 865176] pkey_mprotect(0x7f5d8a1ec000, 268435456, PROT_NONE, 1) = 0
[pid 865176] pkey_mprotect(0x7f5d821ec000, 134217728, PROT_NONE, 1) = 0
[pid 865176] pkey_mprotect(0x62340000, 536870912, PROT_READ|PROT_WRITE|PROT_EXEC, 1) = 0
[pid 865194] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_PKUERR, si_addr=0x7f5d8a1ec010, si_pkey=1} ---

Also I found others related issues:
https://github.com/denoland/rusty_v8/issues/1381
https://github.com/denoland/deno_core/issues/1091
Reply all
Reply to author
Forward
0 new messages