|
| 1 | +# Android Crash Fix — Perry v0.4.26–v0.4.29 |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Android UI apps crashed with `SIGSEGV at getLevelInfo+44` when accessing module-level arrays from UI pump tick callbacks. The root cause was the perry-native thread's arena being freed after `main()` returned. |
| 6 | + |
| 7 | +## Root Cause |
| 8 | + |
| 9 | +On Android, Perry's init chain runs on a background thread ("perry-native"): |
| 10 | + |
| 11 | +``` |
| 12 | +Kotlin PerryActivity.startNative() |
| 13 | + → Thread { PerryBridge.nativeMain() } |
| 14 | + → main() // compiled TypeScript entry point |
| 15 | + → _perry_init_labels_ts() // initializes thresholds[], levels[] |
| 16 | + → App({ ... }) // sets up UI + timer pump |
| 17 | + → main() returns |
| 18 | + → thread exits ← ARENA DROPPED HERE |
| 19 | +``` |
| 20 | + |
| 21 | +After `main()` returns, the perry-native thread exits. Rust's thread-local storage cleanup runs the arena's `Drop`, which **frees all memory blocks** — including module-level arrays (`thresholds`, `levels`) that were allocated during init. |
| 22 | + |
| 23 | +The UI thread's 8ms timer pump then calls `getLevelInfo()`, which dereferences the now-freed array pointers → **SIGSEGV**. |
| 24 | + |
| 25 | +This never happened on macOS/iOS because `App()` blocks forever in the event loop — the thread never exits, so the arena is never dropped. |
| 26 | + |
| 27 | +## Fix |
| 28 | + |
| 29 | +**`crates/perry-ui-android/src/lib.rs`**: After `main()` returns, park the thread forever instead of letting it exit: |
| 30 | + |
| 31 | +```rust |
| 32 | +main(); |
| 33 | +// Park thread — arena holds module-level objects needed by UI thread |
| 34 | +loop { std::thread::park(); } |
| 35 | +``` |
| 36 | + |
| 37 | +## Additional Fixes in v0.4.26–v0.4.29 |
| 38 | + |
| 39 | +| Version | Fix | |
| 40 | +|---------|-----| |
| 41 | +| v0.4.26 | Skip `strip_duplicate_objects_from_lib` on Android — `js_nanbox_*` symbols were stripped from UI lib while standalone runtime was skipped | |
| 42 | +| v0.4.27 | `JNI_GetCreatedJavaVMs` stub — `jni-sys` extern ref unsatisfied on Android (no `libjvm.so`, `libnativehelper` only at API 31+) | |
| 43 | +| v0.4.28 | Module-level arrays with `Unknown`/`Any` type loaded as F64 instead of I64 — now arrays/closures/maps/sets always use I64 | |
| 44 | +| v0.4.29 | Thread-local arena freed after `main()` returned + `-Bsymbolic` linker flag to prevent ELF symbol interposition | |
| 45 | + |
| 46 | +## How to Rebuild for Android |
| 47 | + |
| 48 | +```bash |
| 49 | +# 1. Ensure perry v0.4.29+ is installed |
| 50 | +perry --version # should show 0.4.29 |
| 51 | + |
| 52 | +# 2. Cross-compile the Android UI library (one-time) |
| 53 | +ANDROID_NDK_HOME=$HOME/Library/Android/sdk/ndk/28.0.12433566 \ |
| 54 | + cargo build --release -p perry-ui-android --target aarch64-linux-android |
| 55 | + |
| 56 | +# 3. Compile your app |
| 57 | +ANDROID_NDK_HOME=$HOME/Library/Android/sdk/ndk/28.0.12433566 \ |
| 58 | + perry src/main.ts --target android -o android-build/app/src/main/jniLibs/arm64-v8a/libperry_app.so |
| 59 | + |
| 60 | +# 4. Clean Gradle build (important — clears cached .so) |
| 61 | +cd android-build && ./gradlew clean assembleDebug |
| 62 | +``` |
0 commit comments