Skip to content

v24.x: wasm jit_page_->allocations_.erase(addr) == 1 CHECK, fixed upstream in V8 9b8ca54d5a #66366

Description

@owen-harborcoat

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?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions