Mob.PostMortem.Android (mob v0.9.7)

Copy Markdown View Source

ApplicationExitInfo-backed post-mortem ingest for Android.

Pulls the OS-held history of process exits via ActivityManager.getHistoricalProcessExitReasons (available on Android 11 / API 30 and later), filters against a persistent marker so the drain returns each exit once across boots, keeps each one in Mob.PostMortem.Journal until it has been observed, and hands it to Mob.Defect.emit_appexit_reason/1 as a capsule on Mob.Defect.Bus.

What the OS gives us

ApplicationExitInfo records the reason a previous process instance of this app died — an ANR, a crash, an OOM, a user-initiated kill. The list survives reboots and app updates, and the OS trims it eventually (usually 16 entries per app). We do NOT run every boot hoping to catch a live incident: we sweep whenever the caller does and take whatever the OS still holds since the last sweep.

Persistent marker

Without a marker, every sweep on a fresh install re-emits the entire historical list; every subsequent boot would re-emit the same entries. The native NIF persists the highest-seen exit timestamp to <filesDir>/mob_post_mortem_appexit_marker.txt and filters the OS list to entries strictly newer. First sweep on a fresh install still emits the current history (that is the point of a first sweep); every subsequent drain returns only what accumulated since.

Until observed, not once

The marker advances when the NIF returns an exit, so the drain is destructive: the capsule on this boot's bus is the only copy, and a boot that dies before anyone looks — mix mob.connect restarts the app, for one — would take the exit with it. So each drained exit is written to Mob.PostMortem.Journal before it is emitted, and every sweep, in this boot or a later one, emits it again until its capsule was handed to a subscriber when emitted or returned by Mob.Defect.Bus.recent/1. Within one boot a re-sweep emits nothing it already emitted.

Platform gating

The NIF is registered on both platforms (iOS returns [] unconditionally — the iOS substrate is MetricKit, MOB-179). This module gates on :mob_nif.platform() == :android so a caller on iOS or in a host test does no work at all. On an Android device below API 30 the native side also returns [] — the API was added in Android 11.

Redaction

The emitted capsule carries: numeric reason code, pid (an OS identifier, not user data), timestamp, process name (normally the app's package name; also OS-controlled), and a short OS-generated description string like "remote process crash". NOT included this phase: the trace file contents an ANR carries — those can hold app strings and need the same Mob.Agent.Receipt.summarize_error/3-style discipline before they can safely reach the bus.

Summary

Functions

Sweep any ApplicationExitInfo entries the OS has recorded since the persisted marker was last written, and every earlier one not yet observed.

Functions

sweep()

@spec sweep() :: [Mob.Defect.Capsule.t()]

Sweep any ApplicationExitInfo entries the OS has recorded since the persisted marker was last written, and every earlier one not yet observed.

Returns the list of capsules emitted: unobserved journaled entries first, then new ones in the order the NIF returned them (typically OS-chronological). Empty on iOS, on Android < 11, on a host test with the NIF not loaded, and when nothing is new or waiting to be observed.