Anatomi Startup Crash Loop Pasca Deployment
Migrasi skema database lokal atau perubahan struktur serialisasi state sering kali menjadi penyebab utama startup crash loop pada aplikasi React Native. Ketika rilis baru diluncurkan, fungsi inisialisasi runtime langsung mengeksekusi proses de-serialisasi data lokal (seperti MMKV, WatermelonDB, atau SQLite). Jika skema baru berasumsi bahwa suatu kolom atau property bertipe non-nullable sementara data lama bernilai undefined atau memiliki struktur berbeda, JavaScript engine melempar unhandled exception secara sinkron di fase entry point.
Dampaknya, aplikasi mengalami terminasi paksa (crash on launch) dalam hitungan milidetik setelah proses OS dimulai. Pengguna terjebak dalam siklus di mana aplikasi langsung menutup setiap kali dibuka kembali.
Limitasi Remote Config dan Over-The-Air (OTA) Update
Banyak tim mengandalkan Remote Config (Firebase) atau Over-The-Air (OTA) updates (CodePush atau Expo Updates) sebagai garis pertahanan darurat. Namun, strategi ini gagal total ketika insiden startup crash terjadi. Alasannya:
- Ketiadaan Network Window: Eksekusi JS thread terhenti sebelum modul networking (seperti
fetchatauXMLHttpRequest) sempat menginisiasi koneksi HTTP ke server OTA. - Bridge/JSI Breakdown: Jika crash terjadi di level inisialisasi native module atau sinkronisasi awal JSI (JavaScript Interface), runtime React Native dihentikan oleh OS sebelum bundle update sempat diverifikasi dan dipasang ke disk.
- Dead-on-Arrival: Mekanisme kill-switch berbasis API membutuhkan minimal satu siklus event loop yang bersih untuk membaca payload server. Jika runtime mati sinkron saat mengevaluasi
index.js, logika tersebut tidak akan pernah dieksekusi.
Ketika aplikasi mati sebelum runtime siap, pemulihan harus dilakukan sepenuhnya di sisi klien (client-side mitigation) menggunakan status lokal yang persisten.
Pola Crash Counter: Deteksi Kegagalan Beruntun
Solusi deterministik untuk menangani crash loop adalah menerapkan pola Startup Crash Counter. Pola ini memantau siklus hidup aplikasi dengan aturan:
- Baca counter insiden sebelum proses inisialisasi state utama dimulai.
- Jika counter melampaui batas toleransi (misalnya:
threshold >= 3), alihkan aplikasi ke Emergency Safe Mode. - Jika di bawah ambang batas, inkremen nilai counter dan simpan secara sinkron.
- Jalankan timer stabilisasi (misalnya: 5-10 detik). Jika aplikasi tetap berjalan tanpa crash fatal hingga timer selesai, setel counter kembali ke
0(clean exit/stable runtime).
Karena pembacaan harus instan dan sinkron sebelum engine me-render komponen berat, instance MMKV khusus atau native key-value storage (SharedPreferences / NSUserDefaults) adalah pilihan ideal.
Implementasi Entry Point (`index.js`)
import { AppRegistry } from 'react-native';
import { MMKV } from 'react-native-mmkv';
import App from './src/App';
import EmergencyRecoveryApp from './src/EmergencyRecoveryApp';
import { name as appName } from './app.json';
const bootStorage = new MMKV({ id: 'boot-monitor' });
const CRASH_KEY = 'consecutive_crashes';
const THRESHOLD = 3;
const STABILIZATION_DELAY_MS = 6000;
function evaluateBootHealth() {
const crashes = bootStorage.getNumber(CRASH_KEY) ?? 0;
if (crashes >= THRESHOLD) {
return { shouldEnterRecovery: true, crashCount: crashes };
}
// ponytail: increment synch; native-bridge crash before JS evaluates won't trigger this.
bootStorage.set(CRASH_KEY, crashes + 1);
const timer = setTimeout(() => {
bootStorage.set(CRASH_KEY, 0);
}, STABILIZATION_DELAY_MS);
return { shouldEnterRecovery: false, crashCount: crashes };
}
const bootStatus = evaluateBootHealth();
const RootComponent = bootStatus.shouldEnterRecovery
? EmergencyRecoveryApp
: App;
AppRegistry.registerComponent(appName, () => RootComponent);
Implementasi Emergency Recovery Mode
Saat flag darurat aktif, aplikasi tidak boleh me-render tree utama yang memanggil store (Redux, Zustand, WatermelonDB). Sebagai gantinya, EmergencyRecoveryApp memuat UI minimal berbasis native components tanpa dependensi state kompleks. UI ini mengeksekusi reset state lokal.
Kode Safe-Fallback UI
import React, { useState } from 'react';
import { StyleSheet, Text, View, Pressable, ActivityIndicator } from 'react-native';
import { MMKV } from 'react-native-mmkv';
const bootStorage = new MMKV({ id: 'boot-monitor' });
export default function EmergencyRecoveryApp() {
const [resetting, setResetting] = useState(false);
const [completed, setCompleted] = useState(false);
const handleStatePurge = async () => {
setResetting(true);
try {
// 1. Purge general application storage
const appStorage = new MMKV({ id: 'app-storage' });
appStorage.clearAll();
// 2. Clear crash monitor state
bootStorage.delete('consecutive_crashes');
// ponytail: WatermelonDB/SQLite deletion requires native file unlink if driver locks up.
setCompleted(true);
} catch (error) {
console.error('State purge failed:', error);
} finally {
setResetting(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Mode Pemulihan Sistem</Text>
<Text style={styles.body}>
Aplikasi mendeteksi kegagalan berulang saat proses startup. Data cache lokal
mungkin tidak kompatibel dengan pembaruan saat ini.
</Text>
{completed ? (
<Text style={styles.success}>State berhasil direset. Silakan restart aplikasi.</Text>
) : (
<Pressable
style={styles.button}
onPress={handleStatePurge}
disabled={resetting}
>
{resetting ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>Reset Data Lokal & Pulihkan</Text>
)}
</Pressable>
)}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, backgroundColor: '#0f172a' },
title: { fontSize: 20, fontWeight: '700', color: '#f8fafc', marginBottom: 12 },
body: { fontSize: 14, color: '#94a3b8', textAlign: 'center', marginBottom: 24, lineHeight: 20 },
button: { backgroundColor: '#ef4444', paddingVertical: 12, paddingHorizontal: 24, borderRadius: 8 },
buttonText: { color: '#ffffff', fontWeight: '600' },
success: { color: '#22c55e', fontWeight: '600', textAlign: 'center' },
});
Pelacakan Telemetri ke Sentry dan Datadog
Ketika aplikasi masuk ke EmergencyRecoveryApp, jaringan telemetri harus diinisialisasi secara terkontrol untuk mengirim diagnostik tanpa memicu crash sekunder.
- Isolated Payload: Jangan kirim state global yang korup ke Sentry/Datadog. Kirim metadata ringkas: versi bundle, jumlah crash beruntun, serta last known state migration version.
- Custom Tagging: Pasang tag eksplisit seperti
emergency_recovery: truedancrash_count: 3agar insiden dapat dikelompokkan secara terpisah di dashboard error tracking. - Manual Flush: Karena aplikasi berada dalam safe-mode, panggil flush SDK secara eksplisit sebelum komponen dimatikan atau direstart oleh pengguna:
import * as Sentry from '@sentry/react-native';
export function reportRecoveryTriggered(consecutiveCrashes) {
Sentry.withScope((scope) => {
scope.setLevel('fatal');
scope.setTag('recovery_mode', 'active');
scope.setExtra('consecutive_crashes', consecutiveCrashes);
Sentry.captureMessage('Startup crash loop threshold exceeded. Safe mode triggered.');
});
}
Pencegahan Regresi Skema State di Pipeline CI/CD
Menghindari crash loop di level produksi memerlukan validasi otomatis sebelum deployment disetujui. Langkah pencegahan yang wajib diterapkan di pipeline integrasi:
- Validasi Skema Parsial dengan Zod: Hindari blind casting data dari local storage. Terapkan schema parser dengan
safeParsedan berikan nilai fallback default jika payload tidak valid secara struktural. - Automated Migration Unit Tests: Jalankan unit test spesifik yang membaca mock data versi skema lama, kemudian jalankan fungsi migrasi skema WatermelonDB/SQLite, lalu validasi integritas struktur akhir.
- Storage Migration Matrix Test: Uji migrasi dari versi rilis n-1, n-2, dan n-3 langsung ke versi terbaru di CI menggunakan runner Jest/Node.
Crash loop saat inisialisasi adalah skenario terburuk pada aplikasi mobile karena menutup akses interaksi pengguna secara total. Memasang isolasi safe mode berbasis native counter memastikan aplikasi tetap memiliki jalur pemulihan mandiri tanpa harus menunggu intervensi update manual dari App Store atau Play Store.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!