getsentry / getsentry/sentry-react-native

Fatal 'JavaCallback was already settled' from getNewScreenTimeToDisplay under rapid navigation (Android, Expo, new arch)

Chiusa
#6,709 2 commenti 0 reazioni 1 assegnatario Rivendicata da @antonis Vedi su GitHub
Bug React-Native
Lingua principale
TypeScript
Stelle
1.8k
Fork
366
Merge medio
1g 5h
PR unite (30g)
93

Descrizione

### OS

Android

### Platform

Hermes, New Architecture

### SDK Version

8.19.0

### React Native Version

0.85.3 (Expo SDK 56, expo-modules-core 56.0.19)

### Steps to Reproduce

Not deterministic — it is a race, but the shape that reproduces it in our production fleet:

1. `Sentry.init` with `reactNavigationIntegration({enableTimeToInitialDisplay: true})`, container registered via `registerNavigationContainer` (expo-router).
2. App with 4 native bottom tabs (`react-native-bottom-tabs`) sharing a header.
3. Switch tabs rapidly and/or push a modal right after a tab switch (our events consistently show a paywall modal being pushed immediately after navigation — breadcrumbs end with the new screen's mount logs).

### Expected Result

TTID measurement completes or is dropped silently when navigations race.

### Actual Result

Fatal crash reported through `onerror`:

```
Error: Non-js exception: JavaCallback was already settled. Cannot invoke it again
```

Event tags point at the SDK's own TurboModule call:

- `turbo_module.name: RNSentry`
- `turbo_module.method: getNewScreenTimeToDisplay`
- `mechanism: onerror`, `handled: no`, `level: fatal`, Hermes, Android 12–16

Volume: 23 users in 4 days in production (it likely existed longer — our DSN was broken before that, so the SDK could not report its own crash).

### Analysis

`reactnavigation.ts` (`updateLatestNavigationSpanWithCurrentRoute`) calls `NATIVE.getNewScreenTimeToDisplay()` on **every** navigation state change while `enableTimeToInitialDisplay` is enabled. The Android implementation appears to keep a single stored callback to be settled on the next frame; when two navigation state changes race, the same callback gets settled twice. On Expo (new architecture) the promise guard in `expo-modules-core` (`PromiseImpl` — "was already settled") turns the second settle into the fatal above.

No matching fix found in the changelog up to 8.26.0, and no existing issue mentions `getNewScreenTimeToDisplay`, so filing this one.

### Workaround

`enableTimeToInitialDisplay: false` removes the crashing path entirely (confirmed: the call is gated only by that option).

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.