Version
v24.21.0 (V8 13.6.233.17). Not on v22.23.0 or v26.10.0.
Platform
Linux x86_64, GitHub-hosted ubuntu-24.04 runners (4 vCPU)
Subsystem
deps/v8, wasm
What steps will reproduce the bug?
Splitting this out of #64500 as @gadicc asked: that SIGSEGV turned out to be hardware, but the CHECK @rainder posted there is a real V8 bug. It's fixed upstream in V8, just not in 24.x (build results at the bottom).
My trigger is @wasmer/sdk 0.18.0, a wasm-bindgen module with shared memory instantiated on the main thread and on its worker_threads. The loop creates a client, kills a running guest, closes the client (its workers get terminate()d), and repeats with nothing else alive in between:
git clone https://github.com/owen-harborcoat/wasmer-agent-sandbox && cd wasmer-agent-sandbox
pnpm install
node spikes/2026-09-27-sdk-0.18-worker-init-hang/repro.mjs 30 out kill-close
About 5% of fresh processes abort, usually around iteration 11, so run it in a loop.
How often does it reproduce? Is there a required condition?
20 jobs × 10 fresh processes × 30 iterations per arm:
|
failed jobs |
CHECK / SIGSEGV |
| 24.21.0 |
17 / 40 |
11 / 5 |
24.21.0 --no-memory-protection-keys |
10 / 20 |
5 / 3 |
24.21.0 --no-wasm-code-gc |
2 / 20 |
0 / 0 |
| 22.23.0 |
6 / 20 |
0 / 0 |
| 26.10.0 |
4 / 20 |
0 / 0 |
The failures that aren't CHECKs or SIGSEGVs are SDK bugs, not Node. At 24's rate (~5% of processes), 26.10.0 should have crashed about 9 times in its 185 processes, and it didn't crash at all. It never happens while one client stays open for the whole process (0 in 6,000 iterations), which keeps its instances, and so the shared import wrappers, alive.
What is the expected behavior? Why is that the expected behavior?
No V8 fatal error.
What do you see instead?
# Check failed: jit_page_->allocations_.erase(addr) == 1.
On a worker thread:
#2 v8::internal::ThreadIsolation::JitPageReference::UnregisterAllocation(unsigned long)
#3 v8::internal::ThreadIsolation::UnregisterWasmAllocation(unsigned long, unsigned long)
#4 v8::internal::wasm::WasmCodeAllocator::FreeCode(...)
#5 v8::internal::wasm::WasmImportWrapperCache::Free(...)
#6 v8::internal::wasm::WasmEngine::FreeDeadCodeLocked(...)
#7 v8::internal::wasm::WasmEngine::FreeDeadCode(...)
#8 v8::internal::wasm::WasmCode::DecrementRefCount(...)
#9 v8::internal::wasm::WasmCodeRefScope::~WasmCodeRefScope()
#10 v8::internal::Runtime_TierUpWasmToJSWrapper(int, unsigned long*, v8::internal::Isolate*)
The SIGSEGVs go through the same frames to FreeDeadCodeLocked and die in NativeModule::FreeCode → RecursiveMutex::Lock.
Additional information
This matches v8/v8@9b8ca54d5a ("[wasm] Fix lookup of wrappers marked is_dying", crbug 409379692). Runtime_TierUpWasmToJSWrapper calls WasmImportWrapperCache::MaybeGet, which in 24.x still does WasmCodeRefScope::AddRef(it->second) before checking is_dying(). So a wrapper that code GC is already freeing on another thread gets freed a second time when the scope unwinds. Import wrappers became per-process in v8/v8@a5999be590, which is in 13.6 but not in the 12.4 that 22.x ships. That would explain why 22.x doesn't hit it.
It isn't in v24.x or v24.x-staging. I built v24.x-staging (13987f4) from source twice, once as is and once with 68210d500a then 9b8ca54d5a on top (both apply cleanly, only deps/v8/src/wasm), and ran the same repro, 20 jobs × 10 processes each:
| v24.x-staging 13987f4 |
processes |
CHECK / SIGSEGV |
| as is |
149 |
3 / 5 |
| + 68210d500a, 9b8ca54d5a |
191 |
0 / 0 |
Workflow: https://github.com/owen-harborcoat/wasmer-agent-sandbox/blob/main/.github/workflows/node-backport.yml. The GH runners are ephemeral, so this isn't one bad machine. Could these two get cherry-picked to v24.x?
Version
v24.21.0 (V8 13.6.233.17). Not on v22.23.0 or v26.10.0.
Platform
Subsystem
deps/v8, wasm
What steps will reproduce the bug?
Splitting this out of #64500 as @gadicc asked: that SIGSEGV turned out to be hardware, but the CHECK @rainder posted there is a real V8 bug. It's fixed upstream in V8, just not in 24.x (build results at the bottom).
My trigger is
@wasmer/sdk0.18.0, a wasm-bindgen module with shared memory instantiated on the main thread and on its worker_threads. The loop creates a client, kills a running guest, closes the client (its workers getterminate()d), and repeats with nothing else alive in between:About 5% of fresh processes abort, usually around iteration 11, so run it in a loop.
How often does it reproduce? Is there a required condition?
20 jobs × 10 fresh processes × 30 iterations per arm:
--no-memory-protection-keys--no-wasm-code-gcThe failures that aren't CHECKs or SIGSEGVs are SDK bugs, not Node. At 24's rate (~5% of processes), 26.10.0 should have crashed about 9 times in its 185 processes, and it didn't crash at all. It never happens while one client stays open for the whole process (0 in 6,000 iterations), which keeps its instances, and so the shared import wrappers, alive.
What is the expected behavior? Why is that the expected behavior?
No V8 fatal error.
What do you see instead?
On a worker thread:
The SIGSEGVs go through the same frames to
FreeDeadCodeLockedand die inNativeModule::FreeCode→RecursiveMutex::Lock.Additional information
This matches v8/v8@9b8ca54d5a ("[wasm] Fix lookup of wrappers marked is_dying", crbug 409379692).
Runtime_TierUpWasmToJSWrappercallsWasmImportWrapperCache::MaybeGet, which in 24.x still doesWasmCodeRefScope::AddRef(it->second)before checkingis_dying(). So a wrapper that code GC is already freeing on another thread gets freed a second time when the scope unwinds. Import wrappers became per-process in v8/v8@a5999be590, which is in 13.6 but not in the 12.4 that 22.x ships. That would explain why 22.x doesn't hit it.It isn't in v24.x or v24.x-staging. I built v24.x-staging (13987f4) from source twice, once as is and once with
68210d500athen9b8ca54d5aon top (both apply cleanly, onlydeps/v8/src/wasm), and ran the same repro, 20 jobs × 10 processes each:Workflow: https://github.com/owen-harborcoat/wasmer-agent-sandbox/blob/main/.github/workflows/node-backport.yml. The GH runners are ephemeral, so this isn't one bad machine. Could these two get cherry-picked to v24.x?