BridgeJS: Guard FinalizationRegistry deinit callback against Wasm teardown#662
Draft
krodak wants to merge 1 commit intoswiftwasm:mainfrom
Draft
BridgeJS: Guard FinalizationRegistry deinit callback against Wasm teardown#662krodak wants to merge 1 commit intoswiftwasm:mainfrom
krodak wants to merge 1 commit intoswiftwasm:mainfrom
Conversation
b88e3f9 to
539b8f3
Compare
539b8f3 to
f81bdca
Compare
Member
Hmm, is it true? I still think we have double free issues. Let me take a closer look. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Fixes #661.
The shared
swiftHeapObjectFinalizationRegistry(added in #648) crashes intermittently in CI when its callback fires during Node.js process shutdown. V8 GC collects leftoverSwiftHeapObjectwrappers, the finalizer callsstate.deinit(state.pointer)(a Wasm export), but by that point the Wasm linear memory or function table is already torn down. Result:RuntimeError: memory access out of boundsortable index is out of bounds, roughly 15% of CI runs.The
hasReleasedflag prevents double-release but doesn't help here - the object hasn't been released, the Wasm instance is just gone.Fix
Wrap
state.deinit(state.pointer)in try/catch in both theFinalizationRegistrycallback and therelease()method. If Wasm is already torn down, we swallow the error. The process is exiting anyway.An
instanceAliveflag was considered but requires hooking into teardown lifecycle for no real benefit over a simple try/catch. The finalizer is best-effort cleanup by design.Test
testReleaseHandlesThrowingDeinittests therelease()path: JS replaces the wrapper's deinit to throwWebAssembly.RuntimeErrorafter calling the real one, then callsrelease(). Without the try/catch guard, the error propagates and crashes the process.The FinalizationRegistry callback path isn't tested directly because the import-side wrapper uses
passUnretained(no extra retain), so triggering GC on it would over-release and corrupt the Swift heap - a test artifact, not a production scenario. The template change for both paths is identical (try { state.deinit(state.pointer); } catch {}), so therelease()test covers the logic.