Skip to content

feat(crash): add REPORT_AND_CONTINUE JS crash policy - #49

Open
Fiona2016 wants to merge 6 commits into
mainfrom
feat/report-and-continue
Open

Fiona2016 wants to merge 6 commits into
mainfrom
feat/report-and-continue

Conversation

@Fiona2016

@Fiona2016 Fiona2016 commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds JsCrashPolicy.REPORT_AND_CONTINUE: on an uncaught main-thread ArkTS exception the process keeps running (like OBSERVE_ONLY), but the fault is reported as a crash (is_crash: true) into the live RUM session and counted in a view's crash count.

Until now an app had to choose between exiting (REPORT_THEN_EXIT / REPORT_AND_RECOVER) and staying alive with the fault recorded as a plain error (OBSERVE_ONLY). Apps that keep the process alive on purpose — for example alongside another crash reporter's JS crash protection, which still counts the fault as a crash — could not report matching crash numbers.

Behavior

Policy Process RUM records
REPORT_THEN_EXIT (default) exits crash, replayed next launch
REPORT_AND_RECOVER restarts crash (+ crash.recovered)
REPORT_AND_CONTINUE (new) keeps running crash, live session
OBSERVE_ONLY keeps running non-crash error; nothing with trackErrors=false
  • No crash incident is persisted. The in-process hiAppEvent APP_CRASH copy is marked consumed, so it is neither reported twice nor attributed to the previous run's last view.
  • Not gated by RumConfiguration.trackErrors.
  • Either live callback may carry the report; the guard only suppresses the other callback of the same fault, so a fault that recurs (the process survives it) is counted again.
  • A crash raised between stopView and the next startView (e.g. a timer while backgrounded) counts on the most recently closed view, as markCrash already does. Inherited (previous-launch) crashes are excluded and stay viewless, as before.

Shared-code changes (all policies)

  • LiveCrashIncidentGuard is callback-aware and is cleared once a pair completes. The other policies only remember onUnhandledException, so their decisions are unchanged except that a third same-fingerprint onException within 2 s after a completed pair is now reported instead of dropped.
  • Consumed entries: a keep-alive repeat already covered within half the watcher window adds no entry, and the list keeps at most the newest 100 (it could grow unboundedly, with a synchronous sidecar rewrite per fault, under a kept-alive fault loop with varying messages). The in-process APP_CRASH copy was measured to arrive 60–170 ms after the fault.

Known trade-offs (documented, not changed)

  • The consumed entry is recorded before RUM writes the live crash; if that write failed, the crash would not be recovered from the APP_CRASH copy (that copy would be attributed to the previous run's view, which is worse).
  • An app event mapper that drops the live crash error does not undo the view's crash count — the same as every other live error.
  • If a session has expired (15 min idle) the crash starts a new session like any other event; a crash before the first view has no view.

Testing

  • node scripts/unit-node/run.mjs: 295 passed. New tests cover both callback orders, onException-only builds, recurring faults, completed-pair reset, fault-loop consumed growth and the cap, gap crashes on the last closed view, inherited crashes staying viewless, and OBSERVE_ONLY controls; each new behavior test was checked to fail with its fix reverted.
  • assembleHar core / rum / crash / trace, UnitTestBuild and the entry HAP build clean.
  • Emulator (HarmonyOS 6.1.1 / API 24), local HARs, REPORT_AND_CONTINUE + trackErrors=false, both SDK init orders: the process survived every fault; each fault produced exactly one is_crash error in the faulting session (checked in the RUM backend), the view's crash count was 1, relaunches showed no extra copies, and both the second callback and the APP_CRASH copy were deduplicated. Negative control OBSERVE_ONLY + trackErrors=false: zero errors.
  • Reviewed in four independent rounds (correctness, feature interactions, a second model, and a focused pass on the consumed cap); all verified findings are addressed above.

Uncaught main-thread ArkTS exceptions could either exit the process
(REPORT_THEN_EXIT / REPORT_AND_RECOVER) or keep it alive and be reported
as a non-crash error (OBSERVE_ONLY). Apps that keep the process alive on
purpose, e.g. alongside another crash reporter's JS crash protection,
had no way to keep running while still counting the fault as a crash.

REPORT_AND_CONTINUE keeps the process running and reports an is_crash
error into the live RUM session, counted in the active view's crash
count. Nothing is persisted; the in-process hiAppEvent APP_CRASH copy is
marked consumed so it is not reported again (or attributed to the
previous run's last view). The report is not gated by trackErrors, and
an OS build that only calls onException still counts the crash.

Also document that OBSERVE_ONLY with trackErrors=false reports nothing.
- Count a surviving crash raised between stopView and the next startView
  (e.g. a timer throwing while backgrounded) on the most recently closed
  view, as markCrash already does; it was written as a viewless error and
  never reached the view crash count.
- Make the live callback guard suppress only the OTHER callback of a fault:
  under REPORT_AND_CONTINUE either callback may arrive first, and a fault
  that repeats (the process survives it) is a new crash, not a duplicate.
  Other policies only remember onUnhandledException, so they are unchanged.
- Skip adding a consumed entry when one inside the watcher's match window
  already covers the fault, so a fault loop no longer grows the list and
  rewrites the sidecar on every repeat.
- Docs and demo: describe the new policy in crash-soft-landing.md and the
  crash README, and allow crash_policy=report_and_continue in the demo app.
…edup

- Route only crashes raised in this launch to lastStoppedView: an inherited
  crash (a previous launch's native crash, freeze or watcher-only JsError
  delivered without a last-view snapshot) stays a viewless error, as on main.
- Count a repeating fault's consumed entry as covered only within half the
  watcher's match window: the watcher matches on the platform's fault time,
  which may trail the callback, and an edge repeat could otherwise leak a
  second copy.
- Forget the live callback guard once a pair is complete, so a new fault of
  the same fingerprint delivered through onException only is not taken for
  the second half of the previous pair.
- Docs: the gap-crash rule holds while the session is active; keep-alive
  policies only write the consumed sidecar.
- Keep at most the newest 100 consumed entries. A kept-alive app that
  faults in a loop with a different message each time produced a new
  fingerprint, a new entry and a full synchronous sidecar rewrite on every
  fault, for 24 h.
- Contain a throw from the live crash report like the other report paths.
- Docs: soft-landing notes the keep-alive policies and what RUM does for
  them; README notes a crash before the first view has no view.
…edup writes

- Release what an error-session-capture session holds as soon as a
  REPORT_AND_CONTINUE crash is reported, like the non-crash live path:
  another observer of the same exception (e.g. a crash reporter exiting
  from its own onException) may still end the process before the jitter.
- Record no consumed entry when trackCrashes is off: without the APP_CRASH
  watcher nothing reads it, and each distinct fault rewrote the sidecar.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant