Skip to content

SIGABRT "DLRT_GetFfiCallbackMetadata" on Android arm64 — jni 0.14.2 PortProxyBuilder lifecycle race #3658

Description

@thinklanguages

Platform

Flutter Mobile Android (arm64)

Obfuscation

Enabled (--obfuscate --split-debug-info)

Debug Info

Enabled (uploaded to Sentry via sentry_dart_plugin)

Doctor

Flutter 3.41.6 • stable • Framework db50e20168 (2026-03-25) • Engine 5cdd32777948fa7a648fac915f8da7120ac7e97a • Dart 3.11.4

Other Error Monitoring

No

Version

sentry_flutter 9.18.0 (also reproduced on 9.16.1). Transitive jni **0.14.2** (via sentry_flutter).

Steps to Reproduce

Unfortunately the crash is not deterministically reproducible — it has fired once in 28 days across our production user base. I can describe the usage pattern that surfaces it:

  1. Initialize Sentry in a release Flutter Android build with scope-sync features enabled:
    await SentryFlutter.init((options) {
      options.dsn = ...;
      options.tracesSampleRate = 1.0;
      options.profilesSampleRate = 0.2;
      options.enableAutoPerformanceTracing = true;
      options.enableUserInteractionTracing = true;
      options.enableAutoSessionTracking = true;
    });
  2. In a normal runtime error path, call Sentry.captureException(...) with a withScope callback that sets multiple scope fields (tags, contexts, user, fingerprint) — we set 5–7 tags + 1–2 contexts per report via sequential scope.setTag(...) / scope.setContexts(...) calls.
  3. Under memory pressure on a low-end Android 11 device (in our case Infinix HOT 12i / arm64), the process aborts with the stack below.

Expected Result

No native abort. Scope-sync callbacks complete without use-after-free, or the native side synchronizes the close with pending Java→Dart callback invocations.

Actual Result

Process aborts with SIGABRT. Native log:

../../../flutter/third_party/dart/runtime/vm/runtime_entry.cc: 5122: error:
Callback invoked after it has been deleted.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 14371 >>> com.<redacted>.<redacted> <<<

backtrace:
  #00  pc 0x000000000004e574  /apex/com.android.runtime/lib64/bionic/libc.so
        (abort+180)
  #01  pc 0x00000000008b73f4  /data/app/.../split_config.arm64_v8a.apk!libflutter.so
        (dart::Assert::Fail(char const*, ...) const+54)
  #02  pc 0x0000000000966c18  /data/app/.../split_config.arm64_v8a.apk!libflutter.so
        (DLRT_GetFfiCallbackMetadata+27394048)
  #03  pc 0x0000000000010460  [anon:FfiCallbackMetadata::TrampolinePage]

Device: Infinix HOT 12i (entry-level MediaTek), Android 11 (SDK 30), arm64.
Foreground crash. Single user, single event in 28 days.

Root cause hypothesis (with independent precedent)

This is a use-after-free of a Dart NativeCallable. The FfiCallbackMetadata::TrampolinePage frame tells us native code invoked the callback after NativeCallable.close() had already freed the metadata slot.

DataDog's Flutter SDK hit the identical fingerprint (same assertion, same TrampolinePage frame) on Android and root-caused it to PortProxyBuilder in jni 0.14.2: the Java proxy that PortProxyBuilder creates to deliver Java callbacks to Dart can hold a function pointer past the Dart-side close, and the next invocation hits the deleted-callback assertion. That issue was introduced the moment they adopted jni 0.14.2 for event mappers.

Our resolved dep tree (flutter pub deps):

sentry_flutter 9.18.0
    [flutter flutter_web_plugins sentry package_info_plus meta ffi collection web jni]
jni 0.14.2
    [args ffi meta package_config path plugin_platform_interface]

Per the sentry-dart CHANGELOG, 9.x progressively moved scope sync to JNI: setExtra, setTag, setContexts, setUser, setBreadcrumb in 9.8.0, init, captureReplay, setReplayConfig in 9.9.0. Our reportError code path issues 5–7 sequential JNI round-trips per exception (multiple setTag + setContexts + setUser + fingerprint). Each round-trip creates or reuses a PortProxyBuilder proxy, giving the race many chances to fire, especially under GC pressure on low-end arm64 devices.

Ask

Either:

  1. Bump the jni dependency to a release with a documented PortProxyBuilder lifecycle fix (if one exists), or
  2. Add close-side synchronization in the sentry-dart Android scope-sync layer so pending Java→Dart proxy invocations cannot fire after the Dart side has closed the NativeCallable.

Happy to test a patch build. Also happy to provide a full (non-redacted) Sentry event share-link privately if that helps — let me know the preferred channel.

Submit a PR?

Maybe — willing to help test, but the fix likely needs to land in jni (PortProxyBuilder) or in sentry-dart's JNI lifecycle handling, both of which are outside areas I'm familiar with.

Related

Metadata

Metadata

Assignees

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions