Skip to content

feat(android): Add experimental out-of-process crash reporting - #6347

Merged
romtsn merged 41 commits into
mainfrom
jpnurmi/feat/native-android
Nov 11, 2026
Merged

romtsn merged 41 commits into
mainfrom
jpnurmi/feat/native-android

Conversation

@jpnurmi

@jpnurmi jpnurmi commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

📜 Description

Adds sentry-android-ndk-outproc as an experimental, opt-in alternative to sentry-android-ndk. The existing inproc integration remains the default.

The new package starts the native crash daemon in a separate, non-exported Android service process on API 30+. When tombstone reporting is enabled on API 31+, the service enriches the native crash event with a matching tombstone before sending it. If enrichment is unavailable, it sends the native crash envelope without it.

NdkIntegration now passes the application context needed to configure the service, while retaining the existing initialization entry point. Both NDK variants share scope synchronization and debug-image support, and are declared mutually exclusive through Gradle capabilities.

💡 Motivation and Context

Provides the Android SDK integration for the native backend:

It allows native crash processing and tombstone enrichment to run outside the main application process. Fully enriched crash reports are delivered at the time of the crash.

See also:

💚 How did you test it?

Covered by the Android E2E tests in getsentry/sentry-native#1725, which build this Java feature branch and verify crash delivery to Sentry with tombstone enrichment enabled and disabled, without restarting the app.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🔮 Next steps

Publish sentry-native-ndk-outproc.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 44e2ee9

@sentry

sentry Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.63.0 (1) release

⚙️ sentry-android Build Distribution Settings

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread settings.gradle.kts Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@romtsn romtsn left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost looks good to me, nice work! My clanker found a couple of more things that are worth addressing I believe:

outproc-android-followup.html

And down there there's also a proposal for how we could restructure and simplify the build. Let's discuss it internally if you've got questions!

@christophaigner

Copy link
Copy Markdown
Member

Let's please also extend the docs here: https://docs.sentry.io/platforms/android/configuration/using-ndk/

@jpnurmi
jpnurmi requested a review from a team as a code owner November 3, 2026 10:50
Run the NDK crash daemon from an isolated Android service process so it can
release the crashing app, merge the resulting tombstone, and send the
enriched envelope before shutdown.
Replace the broad `Throwable` catches in `SentryNdkCrashService` with
the exceptions the guarded code can throw, and restore the interrupt
flag when the service bind is interrupted.
The service was started with `startService` but never stopped, so its
process kept running after `Sentry.close()` or after the app process
died. Call `stopSelf` with the start ID that was current when the
worker started, so a newer start from a relaunched app is not stopped.
A new service process receives `onBind` before `onStartCommand`, so a
worker started from the binder configuration could capture a stale
start ID and never stop the service. Tag each start with its shared
memory path and stop only when the latest start belongs to the
exiting worker.
The marker was written as soon as the merged envelope existed. If the
send then failed with a network error, the daemon discarded the
envelope and the next launch skipped the tombstone as already
reported, so the crash was lost. Write the marker only once the send
succeeds.
`startService` throws when the app starts in the background, and the
started service stopped about a minute after the app left the
foreground, so a later crash found no daemon. Native init then fell
back to fork/exec, which cannot exec `libsentry-crash.so` from inside
the APK with default packaging, and stalled startup for 10 s.

Bind the service with `BIND_AUTO_CREATE` and keep the binding until
`SentryNdk.close()`, so the service process keeps the app's importance
and is not frozen. Let crash processing continue after `onDestroy`,
which runs as soon as a crashed app's binding goes away. Fail NDK init
instead of falling back to fork/exec when the service is unavailable:
below API 30, without a context, or when binding or configuring fails.

Move the inproc `SentryNdkTest` to inproc-only test sources, since
outproc init now requires a context.
The crash service picked the native crash exit info closest to the
event's timestamp and ignored its pid. In a crash loop, the first poll
can run before the current crash has an exit info, and the previous
crash's tombstone was merged instead.

Skip exit infos of other pids. The worker captures the pid when it
starts, so a later configure call cannot change it.
When the app relaunched while the previous daemon was still sending
its crash, or before it noticed that its process was gone, the new
config was stored but no worker started, and the old worker's cleanup
then cleared it. The relaunched app's next crash waited 10 s in the
handler and was lost, and its descriptors leaked.

Keep the per-app daemon state in a config owned by its worker, and
start a worker for each config right away. The old daemon is past
crash processing or only waiting for its process to go away, so the
two share only process-wide native state such as the logger.
`sentry-android-ndk-outproc` depends on
`io.sentry:sentry-native-ndk-outproc`, which is not on Maven Central
yet. Build it from the sentry-native feature branch and publish it to
Maven Local so the Build job can resolve it.

TODO: Revert. This is temporary until sentry-native publishes the artifact.
The tombstone wait and the send shared `shutdownTimeoutMillis`, so a
slow tombstone left the send no time at all.

Wait for the app's exit info instead. The app exits right after
debuggerd writes its tombstone, so the wait ends when the tombstone is
there, capped at the 30 s match tolerance. Stop early when the app did
not crash natively or the tombstone does not parse. Give the send the
full `shutdownTimeoutMillis`, and write the marker for a merged
envelope that was sent or kept for the next launch.
Name the shared-memory file after the app pid and delete leftovers whose
process no longer runs when the crash service starts. A setup that fails
before the daemon attaches would otherwise leave the file behind, while
other processes of a multi-process app keep theirs.
`configureDaemon` left the file descriptors of a binder transaction
open until garbage collection when it rejected the configuration.
Close them before returning.
The crash service polled for a matching tombstone until a wall clock
deadline, so a clock step backward delayed the crash send by the size
of the step. Use `SystemClock.uptimeMillis()`, which also drives
`Thread.sleep()`.
The library loader thread swallowed every `Throwable`, including fatal
errors. Catch only the `UnsatisfiedLinkError` of a missing or broken
library, which `init()` reports later. `ExceptionUtils.rethrowIfFatal`
does not fit, because it rethrows `LinkageError` and would turn a
missing library into an app crash.
A missing or broken native library surfaced only in `initCrashDaemon`,
after the crash service was bound and the IPC fds were detached. The
`UnsatisfiedLinkError` left the service bound with `BIND_IMPORTANT`
and the fds open for the app's lifetime. Load the libraries first, so
the failure leaves nothing behind.
When the crash service matched the app's native crash exit but could
not merge its tombstone, because it stayed unavailable until the
deadline or the merge threw, it sent the native event without writing
the marker. On the next launch, `TombstoneIntegration` then reported
the same crash again as a standalone tombstone event. Write the marker
for a matched tombstone, merged or not.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

jpnurmi added a commit to getsentry/sentry-docs that referenced this pull request Nov 3, 2026
Document the opt-in out-of-process backend of the NDK integration,
which captures native crashes in a separate crash service process and
sends them right away. Cover enabling it with and without the Sentry
Android Gradle Plugin, the API 30+ requirement, and what the separate
process means for the app.

Ref: getsentry/sentry-native#1725
Ref: getsentry/sentry-java#6347
Ref: getsentry/sentry-android-gradle-plugin#1474
Comment thread .github/workflows/build.yml Resolved

@romtsn romtsn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, massive work!

The `ndkBackend` option of the Gradle plugin is parked for now, so the
outproc package is selected through its dependency only.
The NDK now backs the crash IPC with a memfd instead of a file, because
the sparse file could raise `SIGBUS` on a full disk.

Create the memfd before configuring the service and send it over
Binder, which reattaching already does. This removes the file path from
the protocol, and the cleanup of stale files.
…ndroid

# Conflicts:
#	buildSrc/src/main/java/Config.kt
#	gradle/libs.versions.toml
The merge of `origin/main` placed it under `8.63.0`, which was released
without out-of-process crash reporting.
sentry-native now publishes `sentry-native-ndk-outproc` to Maven
Central, so CI no longer builds it from the sentry-native branch into
Maven Local.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Craft's `registry` target fails the release when a published module
has no entry. Also list the module in the README packages table and
the Android issue template.
`appTid` received `System.nanoTime()`, but the daemon expects the
parent thread id. On Android it only names the shared memory file that
the daemon unlinks, and none exists because the memory is a memfd.
New code reads clocks through `io.sentry.time` instead of `SystemClock`.
The poll deadline now comes from the options' monotonic ticker, which
also counts time the device spends suspended.
The exception rules in `AGENTS.md` allow a broad catch only at a
boundary, with a comment on why and what happens on failure. The two
`Exception` catches also call `ExceptionUtils.rethrowIfFatal`, which
restores the interrupt flag of a swallowed `InterruptedException`.
`SentryNdkOutproc.init` only unbound the crash service when native init
threw `IllegalStateException`. Any other throwable, such as an
`UnsatisfiedLinkError`, left the service bound with `BIND_IMPORTANT`,
and the disabled NDK integration never closed it.
Comment thread CHANGELOG.md Resolved
…ndroid

# Conflicts:
#	sentry-android-core/src/main/java/io/sentry/android/core/SentryAndroid.java

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 44e2ee9. Configure here.

Comment thread scripts/update-sentry-native-ndk.sh Resolved
@romtsn
romtsn merged commit 00a30a7 into main Nov 11, 2026
46 checks passed
@romtsn
romtsn deleted the jpnurmi/feat/native-android branch November 11, 2026 13:59
jpnurmi added a commit to getsentry/sentry-docs that referenced this pull request Nov 11, 2026
<!-- Keep the urgency section so automation can prioritize this PR. You
may delete other sections you don't need. -->

## DESCRIBE YOUR PR

Document the opt-in out-of-process backend of the NDK integration, which
captures native crashes in a separate crash service process and sends
them right away. Cover enabling it with and without the Sentry Android
Gradle Plugin, the API 30+ requirement, and what the separate process
means for the app.

See:
- getsentry/sentry-native#1725
- getsentry/sentry-java#6347

## IS YOUR CHANGE URGENT?

Help us prioritize incoming PRs by letting us know when the change needs
to go live.
Select exactly one option. For deadlines, replace `YYYY-MM-DD` with the
due date. You can update this information later by editing the PR
description.

- [ ] Urgent deadline (GA date, etc.): YYYY-MM-DD
- [ ] Other deadline: YYYY-MM-DD
- [x] No deadline: Not urgent, can wait up to 1 week+

## SLA

- Teamwork makes the dream work, so please add a reviewer to your PRs.
- Please give the docs team up to 1 week to review your PR unless you've
supplied a deadline.

Thanks in advance for your help!

## PRE-MERGE CHECKLIST

_Make sure you've checked the following before merging your changes:_

- [x] Checked Vercel preview for correctness, including links
- [x] PR was reviewed and approved by any necessary SMEs (subject matter
experts)
- [ ] PR was reviewed and approved by a member of the [Sentry docs
team](https://github.com/orgs/getsentry/teams/docs)

## LEGAL BOILERPLATE

<!-- Sentry employees and contractors can delete or ignore this section.
-->

Look, I get it. The entity doing business as "Sentry" was incorporated
in the State of Delaware in 2015 as Functional Software, Inc. and is
gonna need some rights from me in order to utilize my contributions in
this here PR. So here's the deal: I retain all rights, title and
interest in and to my contributions, and by keeping this boilerplate
intact I confirm that Sentry can use, modify, copy, and redistribute my
contributions, under Sentry's choice of terms.

## EXTRA RESOURCES

- [Sentry Docs contributor guide](https://docs.sentry.io/contributing/)
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.

3 participants