fix(android): contain panics at the JNI boundary instead of aborting the process
Ten `extern "system"` callbacks are entered by the JVM on arbitrary threads. A panic unwinding out of one crosses the FFI boundary, which Rust answers by aborting: the app vanishes with no Java exception, no attributable stack trace, and no crash report the user can send. For callbacks that fire four times a second during playback that is the worst available failure mode. It was reachable. `nativeOnPositionUpdate` built a fallback Tokio runtime with `Runtime::new().unwrap()` on threads that have none, and `Runtime::new()` fails under exactly the fd exhaustion and thread-spawn refusal Android subjects a media app to. That now logs and drops the report — losing one progress report is recoverable, losing the app is not. Every callback body is wrapped in `jni_guard`, which catches the unwind and logs it. It is a backstop, not a licence to panic: a contained panic still leaves whatever it interrupted half-done. The guard lives in `player::jni_guard` rather than `player::android` because that module is `cfg(target_os = "android")` and so never compiles on the host — which is why its 1575 lines had no tests at all. A tripwire test asserts every entry point wraps its body, so an eleventh callback cannot reintroduce the defect; it reads the source, since exercising the real boundary needs a JVM. Verified with `cargo check --target aarch64-linux-android`.
This commit is contained in:
@@ -28,6 +28,10 @@ pub mod track_switch;
|
||||
#[cfg(test)]
|
||||
mod mpv_backend_test;
|
||||
|
||||
// Panic containment for the JNI boundary. Not gated on the target: the guard
|
||||
// and its tripwire test are exercised on the host, where `android` never builds.
|
||||
pub mod jni_guard;
|
||||
|
||||
// Platform-specific backends
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod android;
|
||||
|
||||
Reference in New Issue
Block a user