Repository navigation
fix(android): flush queued finalizers before a jsi runtime is marked destroying - #95
Open
NathanWalker wants to merge 1 commit into
Open
NathanWalker wants to merge 1 commit into
NathanWalker wants to merge 1 commit into
Conversation
…destroying DestroyRuntime set is_destroying before flushing the finalizer queue. HostObjectProxyPostFinalizer treats is_destroying as "leave the target handle", a rule carried over from napi, where js_free_napi_env frees leftover refs. engine:: handles have no such owner, so every queued proxy cleanup leaked its JS wrapper, and on QuickJS JS_FreeRuntime aborted on list_empty(&rt->gc_obj_list). The queue is normally drained on the next looper tick. A worker that calls close() from onmessage quits its looper first, which drops that tick, so any proxy collected during the handler was still queued at dispose. terminate() from the parent arrives after the tick has run. Flush the queue first, while the runtime is not yet marked destroying. Fixes #94
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
This branch has not been deployed
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.
Fixes #94.
Summary
On jsi, a worker aborts on QuickJS if it calls
close()from itsonmessageafter a Java wrapper was collected in the same handler:JS_FreeRuntimeassertslist_empty(&rt->gc_obj_list).FinalizerQueue, which drains on the next looper tick.close()quits the looper first, so that tick never comes.DestroyRuntimethen flushed the queue, but only after settingis_destroying.is_destroyingis set,HostObjectProxyPostFinalizerleaves the target handle alone. That rule comes from napi, wherejs_free_napi_envfrees leftover refs.engine::handles have no such owner, so the JS wrapper outlived the VM. V8 and JSC only leak it; QuickJS asserts.terminate()from the parent arrives after the tick has run, so it never hit this.DestroyRuntimenow flushes the queue before it setsis_destroying. Nothing else in teardown changes: finalizers that fire later still run inline. The napi tree is unaffected.The PR also adds an Android worker spec:
new java.lang.Object(); gc(); postMessage('closing'); close();.Evidence
Pixel 9 emulator (API 36, arm64), standalone test app.
new java.lang.Object(); gc(); close();inonmessageaborts on every run on the base branch (4 of 4). The same body ending inpostMessage, thenterminate()from the parent, does not.JS_DUMP_LEAKSbuild). It showed one leaked object, the JS wrapper of a Java object. Temporary logging showed the "destroying" branch ofHostObjectProxyPostFinalizerfiring on that thread right before the abort.close()round.Not verified:
OnDisposeRuntimeneutralises proxies, it still takes the "leak" branch. The worker tests here never reached it.Merge Danger
Door: two-way
Blast Radius: jsi runtime dispose: worker
close/terminate, and RN detach once #89 landsAt dispose, queued proxy cleanups now run as a normal drain. They release their wrapper handle and call
makeInstanceWeakon the already-detached Java runtime, which only updates that runtime's maps.