Problem
Sentry Flutter registers the beforeSend callback as a JNI → Dart blocking callback proxy. When the Android native side calls captureEvent, it invokes this callback which re-enters the Dart isolate synchronously.
The underlying issue is in package:jni itself: the blocking callback mechanism uses a simple isOnMainThread() check to decide whether it can enter the target isolate, but this isn't strict enough. The assumptions are violated in app close/reopen flows — the target isolate may already be entered by another thread, causing an assertion failure and crash.
Root Cause (in package:jni)
From dart-lang/native#3250:
The only EnterIsolate call is for blocking callbacks where mayEnterIsolate is true and the invoking thread is not entered into an isolate. mayEnterIsolate is supposed to be true only if the target isolate is owned by the main thread, and the invoking thread is the main thread. But the assertion failure says the target isolate is already entered by another thread.
Looking at how jni is doing the ownership check, it predates the isolate ownership API. package:objective_c uses the isolate ownership API, but package:jni uses a simple main thread check. So the simple main thread check isn't strict enough, and its assumptions are violated in the close/reopen flow.
The fix in package:jni migrates blocking callbacks to the proper isolate ownership API (same approach as package:objective_c).
Impact on Sentry Flutter
Our beforeSend callback registered via JNI is affected by this. Any native Android event capture that triggers the blocking callback during or after an isolate ownership change (e.g., app backgrounding/foregrounding) can crash.
Upstream Fix
Mitigation
We cannot bump jni on the main branch yet, so the mitigation is to move the beforeSend callback logic entirely to Kotlin, avoiding the JNI → Dart cross-boundary call:
// Currently (creates JNI -> Dart callback proxy, vulnerable to isolate crash):
beforeSend.use((cb) {
androidOptions.setBeforeSend(cb);
});
// Mitigation (calls Kotlin callback, no Dart isolate entry needed):
final nativeBeforeSend = native.SentryFlutterPlugin.createBeforeSendCallback();
androidOptions.setBeforeSend(nativeBeforeSend);
By keeping the callback on the native Kotlin side, we avoid the JNI → Dart isolate re-entry entirely, sidestepping the bug until the upstream fix can be adopted.
Context
- Related: DART-221 (allowing Flutter
beforeSend to apply to native events)
- Related: DART-274 (JNI 0.15.0 bump)
Problem
Sentry Flutter registers the
beforeSendcallback as a JNI → Dart blocking callback proxy. When the Android native side callscaptureEvent, it invokes this callback which re-enters the Dart isolate synchronously.The underlying issue is in
package:jniitself: the blocking callback mechanism uses a simpleisOnMainThread()check to decide whether it can enter the target isolate, but this isn't strict enough. The assumptions are violated in app close/reopen flows — the target isolate may already be entered by another thread, causing an assertion failure and crash.Root Cause (in
package:jni)From dart-lang/native#3250:
The fix in
package:jnimigrates blocking callbacks to the proper isolate ownership API (same approach aspackage:objective_c).Impact on Sentry Flutter
Our
beforeSendcallback registered via JNI is affected by this. Any native Android event capture that triggers the blocking callback during or after an isolate ownership change (e.g., app backgrounding/foregrounding) can crash.Upstream Fix
Mitigation
We cannot bump
jnion the main branch yet, so the mitigation is to move thebeforeSendcallback logic entirely to Kotlin, avoiding the JNI → Dart cross-boundary call:By keeping the callback on the native Kotlin side, we avoid the JNI → Dart isolate re-entry entirely, sidestepping the bug until the upstream fix can be adopted.
Context
beforeSendto apply to native events)