Skip to content

Commit 44dcfff

Browse files
committed
docs: Android crash fix writeup for the team (v0.4.26-v0.4.29)
1 parent e69d468 commit 44dcfff

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

docs/android-crash-fix-v0.4.29.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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

Comments
 (0)