Skip to content

Stop the JNI warning about dropping a GlobalRef in a detached thread on Android - #193

Merged
ruccho merged 1 commit into
mainfrom
fix/android-jni-detached-global-ref
Oct 21, 2026
Merged

ruccho merged 1 commit into
mainfrom
fix/android-jni-detached-global-ref

Conversation

@ruccho

@ruccho ruccho commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

On Android, the following warning was logged every frame while recording.

W Unity : [unienc] jni::wrapper::objects::global_ref: Dropping a GlobalRef in a detached thread. Fix your code if this message appears frequently (see the GlobalRef docs).

attach_current_thread() returned a scoped AttachGuard, which detaches a thread that was not attached before (e.g. a tokio worker) when the guard is dropped. Every GlobalRef dropped after the guard was therefore deleted on a detached thread, and the jni crate attached the thread again only to delete it.

 impl Drop for ImageWriterImage {
     fn drop(&mut self) {
         if let Ok(mut env) = attach_current_thread() {   // attach
             Image.close(self.image)
-        }                                                 // detach
-    }                                                     // drop self.image (GlobalRef) -> warning
+        }                                                 // pop local frame (thread stays attached)
+    }                                                     // drop self.image (GlobalRef) -> OK
 }

The same pattern exists in queue_input_image (the image argument is dropped after the locals), the Bgra32 input path, the audio input path and pull_encoded_data_with_codec.

attach_current_thread() now attaches the thread permanently; the jni crate detaches it when the thread terminates. Since detaching no longer releases the local references created on the thread, the function returns a LocalFrameEnv guard that pushes a local reference frame and pops it on drop. The guard dereferences to JNIEnv, so the call sites are unchanged.

-pub fn attach_current_thread() -> Result<AttachGuard<'static>> {
-    vm.attach_current_thread()
+pub fn attach_current_thread() -> Result<LocalFrameEnv> {
+    let env = vm.attach_current_thread_permanently()?;
+    env.push_local_frame(LOCAL_FRAME_CAPACITY)?;
+    Ok(LocalFrameEnv { env })   // pops the frame on drop
 }

Local references must not outlive the guard. This was already required, as they became invalid when the thread was detached.

Evidence

Measured on a real device (samsung SCG13, Android 16), counting the warning in logcat.

Scenario Before After
scripts/android-device-test.sh --release (Bgra32 input), warnings 1661 0
Same, Attached thread debug logs from the jni crate 7484 20
Unity 6000.3 sample scene (Vulkan, HardwareBuffer input), about 45 s of buffering, warnings 4946 0

The device test passes before and after the change. In the Unity build, MediaCodec ran normally after the change and no JNI error or crash was logged.

…on Android

The Android backend attached the calling thread to the JVM only for the scope of each JNI call and detached it when the scope ended. A GlobalRef dropped after that point, such as a field of a struct whose Drop impl attaches the thread or an argument dropped after the locals of a function, was deleted on a detached thread. The jni crate then attached the thread temporarily and logged a warning, which appeared every frame while recording.

Threads are now attached permanently and detached by the jni crate when they terminate. Since detaching no longer releases the local references created on a thread, attach_current_thread pushes a local reference frame and the returned guard pops it on drop.
@ruccho
ruccho marked this pull request as ready for review October 21, 2026 06:13
@ruccho
ruccho requested a review from hkmt-mmy October 21, 2026 06:13
@ruccho
ruccho merged commit e364da2 into main Oct 21, 2026
7 checks passed
@ruccho
ruccho deleted the fix/android-jni-detached-global-ref branch October 21, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants