Postmortem Insiden: Anatomi Crash ABI Mismatch
Penyebaran JavaScript bundle via Over-The-Air (OTA) update tanpa sinkronisasi spesifikasi binary native memicu insiden fatal: aplikasi mengalami crash on boot massal. Masalah ini berakar pada ketidakcocokan Application Binary Interface (ABI) antara JavaScript layer dengan C++ JSI (JavaScript Store Interface) atau platform bridge (JNI di Android dan Objective-C runtime di iOS).
Gejala Error dan Karakteristik Stack Trace
Berbeda dengan runtime error pada JavaScript murni yang dapat ditangkap oleh ErrorBoundary, ABI mismatch membunuh proses aplikasi pada level native. Di Android, logcat menghasilkan java.lang.NoSuchMethodError ketika JNI mencoba memanggil method native yang signature-nya berubah, atau SIGSEGV (Segmentation Fault) di dalam libreactnativejni.so saat pointer C++ JSI mengakses memory address invalid.
// Contoh fatal error logcat pada TurboModule mismatch
FATAL EXCEPTION: main
Process: com.app.production, PID: 14205
java.lang.NoSuchMethodError: no non-static method "Lcom/app/modules/CryptoModule;.decryptData(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V"
at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)Pada iOS, crash bermanifestasi sebagai NSInvalidArgumentException dengan pesan -[NativeSecurityModuleSpec decryptData:]: unrecognized selector sent to instance. Pengguna mengalami penghentian paksa instan begitu aplikasi diinisialisasi.
Root Cause Analysis
Penyebab utama insiden ini adalah validasi versi binary yang hanya mengandalkan semantic version statis pada package.json atau versionName Android tanpa memverifikasi perubahan signature interface native. Ketika branch rilis OTA menyertakan pembaruan dependensi native (misalnya library enkripsi atau analytics) sementara binary yang terpasang di perangkat pengguna masih versi lama, JavaScript bundle mencoba mengeksekusi binding TurboModule yang tidak ada di binary native terpasang.
Mitigasi Darurat dan Strategi Rollback Instan
Saat insiden terjadi, prioritas tunggal adalah menghentikan penyebaran bundle cacat dan mengembalikan perangkat pengguna ke bundle yang kompatibel dengan binary terpasang.
- Hentikan Rilis OTA Seketika: Nonaktifkan deployment aktif pada control plane OTA (misal CodePush atau Expo Updates) agar perangkat yang belum mengunduh bundle tidak terinfeksi.
- Deploy Rollback Bundle: Rilis ulang bundle versi stabil sebelumnya dengan target binary version yang ketat. Jika menggunakan AppCenter CodePush:
# Rollback instan ke rilis sebelumnya untuk target deployment production
appcenter codepush rollback -a <owner>/<app-name> -d Production
# Atau nonaktifkan rilis spesifik yang bermasalah secara langsung
appcenter codepush patch -a <owner>/<app-name> -d Production -l v124 --is-disabled trueJika menggunakan self-hosted bundle server di balik CDN (misal CloudFront atau Cloudflare), lakukan cache invalidation untuk file manifest bundle segera setelah me-revert pointer rilis di object storage.
Peringatan Operasional: Jangan mencoba menyelesaikan crash ABI mismatch dengan mengirimkan perbaikan hotfix JavaScript jika interface native yang dibutuhkan memang tidak terkompilasi di binary client. Solusi definitif untuk client yang sudah mengunduh binary mismatch adalah memaksa fallback ke embedded bundle internal atau mengarahkan pengguna ke app store update.
Observabilitas Real-Time: Membedakan Native Crash vs JS Exception
Tantangan utama mendeteksi ABI mismatch adalah pelaporan metrik yang bias. Dashboard error JS sering kali tidak menunjukkan lonjakan anomali karena thread JavaScript langsung terbunuh sebelum client SDK pelaporan sempat memproses event exception.
Konfigurasi Crash Reporting (Sentry / Crashlytics)
Pantau metrik Crash-Free Sessions pada level native OS, bukan hanya breadcrumb JavaScript. Konfigurasikan pelaporan native crash dengan tag runtime version yang eksplisit:
- Filter stack trace yang mengandung modul runtime inti:
libjsc.so,libhermes.so,libreactnativejni.so, danReactCommon. - Korelasikan lonjakan native crash dengan metadata deployment OTA. Tambahkan tag
bundle_idatauota_releasepada scope crash reporting native saat runtime JavaScript pertama kali dimuat.
// Inisialisasi tagging pada native-to-JS bridge awal
import * as Sentry from "@sentry/react-native";
import { NativeModules } from "react-native";
const { RuntimeMetadata } = NativeModules;
Sentry.configureScope((scope) => {
scope.setTag("native_abi_hash", RuntimeMetadata.expectedAbiHash);
scope.setTag("active_bundle_hash", RuntimeMetadata.activeBundleHash);
});Set alarm pada sistem monitoring (misal Datadog atau Sentry Alerts) dengan kondisi: trigger alert aktif jika rasio unhandled native crash meningkat >1% dalam rolling window 5 menit pasca-perubahan metadata manifest OTA.
Otomasi Pencegahan CI/CD: Deterministic Runtime Hashing
Mencegah insiden terulang memerlukan mekanisme verifikasi deterministik di pipeline CI/CD. Semantic versioning manual (seperti 1.4.0) rentan kelalaian manusia. Solusinya adalah menghasilkan hash unik berbasis file-file yang menentukan ABI native.
Komponen Pembentuk ABI Hash
Sebuah runtime ABI hash dihitung dari perubahan pada:
- File lock dependensi:
package-lock.jsonatauyarn.lock(hanya dependensi native). - Direktori platform native:
android/(termasukbuild.gradle) danios/(termasukPodfile.lock). - Versi C++ runtime dan konfigurasi Hermes/JSI.
Implementasi Skrip Verifikasi CI
Gunakan script Node.js berikut di pipeline CI/CD sebelum bundle dipromosikan ke deployment server OTA. Script ini menghitung SHA-256 dari dependensi native dan memeriksa kecocokannya dengan target binary.
// scripts/generate-runtime-hash.js
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
function getNativeFilesChecksum() {
const hash = crypto.createHash("sha256");
// Daftarkan file kritis pembentuk ABI native
const criticalFiles = [
"android/app/build.gradle",
"android/build.gradle",
"ios/Podfile.lock",
"package.json"
];
for (const relativePath of criticalFiles) {
const fullPath = path.resolve(process.cwd(), relativePath);
if (fs.existsSync(fullPath)) {
const content = fs.readFileSync(fullPath);
hash.update(content);
}
}
return hash.digest("hex").substring(0, 16);
}
const runtimeHash = getNativeFilesChecksum();
console.log(`Computed Runtime ABI Hash: ${runtimeHash}`);
// Ekspor ke format build artifact untuk divalidasi oleh CI runner
fs.writeFileSync("runtime-hash.env", `RUNTIME_ABI_HASH=${runtimeHash}\n`);Eksekusi Validasi di Pipeline
Pada pipeline deployment OTA, bandingkan RUNTIME_ABI_HASH bundle yang akan dirilis dengan hash binary yang sedang aktif di store. Jika terjadi perbedaan hash, pipeline wajib gagal (exit code 1) dan menolak upload OTA.
# Cuplikan step verifikasi di pipeline CI/CD
- name: Verify ABI Compatibility
run: |
node scripts/generate-runtime-hash.js
SOURCE_HASH=$(grep RUNTIME_ABI_HASH runtime-hash.env | cut -d '=' -f2)
STORE_BINARY_HASH=$(curl -s https://api.internal/binary-metadata/latest/android | jq -r .abiHash)
if [ "$SOURCE_HASH" != "$STORE_BINARY_HASH" ]; then
echo "FATAL: ABI Mismatch detected!"
echo "Source Hash: $SOURCE_HASH | Store Binary Hash: $STORE_BINARY_HASH"
echo "Perubahan native terdeteksi. Deployment OTA dibatalkan. Wajib rilis binary baru via Store."
exit 1
fiRuntime Fail-Safe Guard pada Klien
Sebagai pertahanan terakhir (defense-in-depth), tanamkan validasi hash di level native client saat aplikasi booting sebelum bundle dieksekusi oleh engine JS:
// Modul Native Android: Guard eksekusi bundle
public class BundleSafetyValidator {
public static boolean isBundleCompatible(String bundleAbiHash, String nativeAbiHash) {
if (bundleAbiHash == null || !bundleAbiHash.equals(nativeAbiHash)) {
// Abort custom bundle, fallback ke bundle default di APK assets
return false;
}
return true;
}
}Jika native guard mendeteksi ketidaksesuaian hash, klien langsung menghapus bundle OTA yang terunduh dan melakukan fallback lokal ke assets embedded bundle bawaan binary. Langkah ini mengeliminasi loop crash total meskipun bundle yang salah terlanjur terdistribusi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!