# Android TV OOM / SIGKILL Diagnosis & Fixes

Added: 2026-06-02 session.

## SIGKILL vs Code Crash — How to Tell

| Signal | Type | Diagnostic |
|--------|------|-----------|
| SIGKILL (signal 9) | Kernel OOM killer | `Sending signal. PID: N SIG: 9`, `Remote process closed the socket`, `Process N exited due to signal 9 (Killed)`, `Failed to transact (-32)`. **No stack trace.** |
| NullPointerException | Code crash | Stack trace with `java.lang.NullPointerException` |
| JsonDecodingException | Serialization crash | Stack trace with `kotlinx.serialization.json.internal.JsonDecodingException` |
| ANR (App Not Responding) | Main thread blocked | `ANR in com.unspooled.app`, `Input dispatching timed out` |

If the **entire device** reboots (not just the app), it's total system memory exhaustion — the kernel itself runs out of memory and the OOM killer can't keep up.

## Root Causes Found in Unspooled

### 1. AIOMeta Catalog Surge (60+ catalogs)
`HomeViewModel.loadHome()` launched all 60+ AIOMeta catalog coroutines inside one `coroutineScope{}`. Even with `MAX_CATALOG_CONCURRENCY=8` semaphore, all coroutines were alive holding response JSON.

**Fix (2026-06-02):**
- `MAX_CATALOG_CONCURRENCY = 4` (was 8)
- `MAX_ADDON_CATALOG_ROWS = 15` cap via `.take(15)` on second batch

### 2. Coil Memory Cache Too Large
25% of heap on a 512MB TV = 128MB for image cache. ExoPlayer/FFmpeg needs 200-500MB for 4K native allocations.

**Fix (2026-06-02):**
- Coil memory cache: 25% → 15% of heap (`Runtime.getRuntime().maxMemory() * 0.15`)
- Disk cache: bumped to v3 to invalidate stale v2 entries

### 3. ART JIT Compiler OOM — Composable Parameter Bloat
`PremiumContentCard` had 27 parameters. JIT compiler allocates ~5MB per compilation instance. 25 catalog rows × ~7 cards = 175 instances = ~875MB.

**Fix (2026-06-02):**
- Wrapped 14 display params into `PremiumContentCardDisplay` `@Stable` data class
- 27 params → 13 params, JIT allocation ~5MB → ~300KB per instance

**Diagnostic signature:** `Compiler allocated 5112KB to compile void com.unspooled.app.ui.cards.PremiumContentCardKt.PremiumContentCard(...)` just before SIGKILL.

## LMKD/BPF Buffer Error

```
lmkd: memevent failed getting memory events: BPF ring buffer message has unexpected size
lowmemorykiller: Failed fetching memory listener events
```

This is a kernel-level issue with the Low Memory Killer Daemon's BPF ring buffer. Known on some MediaTek/AmLogic TV chipsets. When this fails, the kernel can't properly manage memory pressure → OOM is more likely → device reboot. Not fixable from app code.

## Fix Strategy Checklist

When diagnosing Android TV OOM/SIGKILL:

1. Check logcat for SIGKILL vs stack trace
2. Count concurrent coroutine launches on startup (catalogs, health checks, addon fetches)
3. Check Coil memory cache % of heap — TV devices need more for native
4. Check composable parameter counts in LazyRow/LazyColumn — use `grep -c ','` on function signatures
5. Cap external data sources (addon catalogs, image prefetch, health checks)
6. Reduce HTTP concurrency semaphore
