Skip to content

fix(android): stop crash when USB is first used off the main thread - #42

Merged
s00d merged 1 commit into
s00d:mainfrom
barbarbar338:fix/android-jni-class-loader
Sep 11, 2026
Merged

fix(android): stop crash when USB is first used off the main thread#42
s00d merged 1 commit into
s00d:mainfrom
barbarbar338:fix/android-jni-class-loader

Conversation

@barbarbar338

@barbarbar338 barbarbar338 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

On Android, our app crashed the moment it listed serial ports, because it calls available_ports from an async Tauri command (a tokio worker thread):

FATAL EXCEPTION: Thread-3
java.lang.ClassNotFoundException: app.tauri.serialplugin.UsbNative

fd_bridge::cache() looks up UsbNative with FindClass the first time it is used, on whatever thread gets there first. On a thread Rust attached with attach_current_thread, JNI uses the system class loader, which can't see app classes, so the lookup fails. The error is still pending when ? returns early, so ART kills the app when the thread detaches. It only works if the first USB call happens to come from the main thread (e.g. the sync JS commands). Any call from async commands, tokio tasks or spawned threads crashes, and so could the port IO that now runs off the main thread.

The fix: nativeInit is a static native method on UsbNative, so its jclass argument is the class itself, already resolved by the app class loader. The patch keeps a global ref to it there, next to where the JVM is stored, and drops the thread-dependent FindClass. The public API doesn't change. As before, calls that run before bind() return "JNI not initialized".

Testing

  • On a Galaxy Tab A9 (Android 16), release build: before the fix, the app crashed on the first available_ports from a tokio worker. After it, enumeration runs on tokio-rt-worker and returns normally, with nothing in the crash buffer.
  • cargo fmt --all -- --check passes. cargo clippy -p tauri-plugin-serialplugin --target aarch64-linux-android --lib -- -D warnings passes.
  • --all-targets for aarch64-linux-android fails to compile the lib tests (unresolved crate::AtResultFormat, etc.). That happens identically on main, so it's unrelated to this change.

🤖 Generated with Claude Code


Summary by cubic

Fixes an Android crash when serial ports are first used from a non-main thread by caching the UsbNative JNI class in nativeInit instead of looking it up with FindClass on first use.

  • On Rust-attached threads (async commands, tokio tasks), FindClass uses the system class loader, which can't see app classes, so the lookup fails with ClassNotFoundException and ART kills the app at thread detach.
  • nativeInit is a static native method on UsbNative, so its jclass argument is already resolved by the app class loader; the fix keeps a global ref there.
  • Public API is unchanged; calls before bind() still return "JNI not initialized".

Written for commit 0173a5f. Summary will update on new commits.

Review in cubic

fd_bridge resolved UsbNative with FindClass on the first call, which runs on
a Rust thread attached via attach_current_thread. Such threads use the system
class loader, so the lookup throws ClassNotFoundException; the pending
exception survives the early return and ART kills the app when the thread
detaches (e.g. on the first available_ports call).

nativeInit is a static native method on UsbNative, so its jclass argument is
the class itself, resolved by the app class loader. Keep a global ref to it
there and drop the thread-dependent FindClass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 10, 2026 21:06
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread src/android/fd_bridge.rs
Comment thread src/android/fd_bridge.rs
@s00d
s00d merged commit 46797ec into s00d:main Sep 11, 2026
5 checks passed
s00d added a commit that referenced this pull request Sep 11, 2026
Log new_global_ref / get_java_vm errors, distinguish not-ready
messages, and add worker-thread enumerate smoke for #42.

Co-authored-by: Cursor <cursoragent@cursor.com>
@s00d

s00d commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Published in 3.0.7 (release).

Cubic follow-ups from this review are in (b331bc9 / related): clearer UsbNative class-cache init failures + host unit tests + EnumerateWorkerThreadTest for the off-main-thread #42 path.

Also on main / 3.0.7: FakeTransport JNI harness now wires the RX hub correctly (was polling a mock), AT modem sim for AVD, and instrumented suite is green on Pixel_9 emulator (10/10).

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.

3 participants