Skip to content

EXC_BAD_ACCESS in production after upgrading to 0.79.6 #53774

Open

Description

@matinzd

Description

We have got a lot a bunch of unhandled errors in sentry and some users are experiencing crashes after enabling new architecture and moving to RN 0.79.6:

https://lendo.sentry.io/share/issue/15101300672940039f9334b6a3dbd6cf/

Exception 1, Code 1, Subcode 2014344570531660876 >
KERN_INVALID_ADDRESS at 0x1bf463b6e5c6c44c.

Hermes engine in Podfile.lock is also updated to the latest version and it's locked to 0.79.6.

It's happening for 9 users since 2 weeks ago that we released.

Steps to reproduce

This is very hard to reproduce. I am not sure what has happened.

React Native Version

0.79.6

Affected Platforms

Runtime - iOS

Output of npx @react-native-community/cli info

info Fetching system and libraries information...
System:
  OS: macOS 15.6.1
  CPU: (10) arm64 Apple M1 Pro
  Memory: 134.56 MB / 32.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.18.0
    path: ~/.nvm/versions/node/v22.18.0/bin/node
  Yarn:
    version: 1.22.19
    path: ~/.nvm/versions/node/v22.18.0/bin/yarn
  npm:
    version: 10.9.3
    path: ~/.nvm/versions/node/v22.18.0/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.16.1
    path: /Users/xxxx/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.5
      - iOS 18.5
      - macOS 15.5
      - tvOS 18.5
      - visionOS 2.5
      - watchOS 11.5
  Android SDK:
    API Levels:
      - "28"
      - "29"
      - "30"
      - "31"
      - "33"
      - "34"
      - "35"
      - "36"
    Build Tools:
      - 28.0.3
      - 29.0.2
      - 30.0.3
      - 33.0.1
      - 34.0.0
      - 35.0.0
      - 36.0.0
    System Images:
      - android-33-ext4 | Google Play ARM 64 v8a
      - android-34-ext12 | Google Play ARM 64 v8a
      - android-34 | Google APIs ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2024.3 AI-243.26053.27.2432.13536105
  Xcode:
    version: 16.4/16F6
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.11
    path: /usr/bin/javac
  Ruby:
    version: 3.2.2
    path: /Users/xxxx/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 18.0.0
    wanted: 18.0.0
  react:
    installed: 19.0.0
    wanted: 19.0.0
  react-native:
    installed: 0.79.6
    wanted: 0.79.6
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

info React Native v0.81.4 is now available (your project is running on v0.79.6).
info Changelog: https://github.com/facebook/react-native/releases/tag/v0.81.4
info Diff: https://react-native-community.github.io/upgrade-helper/?from=0.79.6&to=0.81.4
info For more info, check out "https://reactnative.dev/docs/upgrading?os=macos".

Stacktrace or Logs

OS Version: iOS 18.6.2 (22G100)
Report Version: 104

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: SEGV_NOOP at 0x1bf463b6e5c6c44c
Crashed Thread: 8

Application Specific Information:
Exception 1, Code 1, Subcode 2014344570531660876 >
KERN_INVALID_ADDRESS at 0x1bf463b6e5c6c44c.

Thread 8 Crashed:
0   KreddyMobile                    0x2039cd910         [inlined] facebook::react::Scheduler::uiManagerDidFinishTransaction::lambda::operator() (Scheduler.cpp:266)
1   KreddyMobile                    0x2039cd910         [inlined] std::__1::__invoke[abi:ne180100]<T> (invoke.h:344)
2   KreddyMobile                    0x2039cd910         [inlined] std::__1::__invoke_void_return_wrapper<T>::__call[abi:ne180100]<T> (invoke.h:419)
3   KreddyMobile                    0x2039cd910         [inlined] std::__1::__function::__alloc_func<T>::operator()[abi:ne180100] (function.h:169)
4   KreddyMobile                    0x2039cd910         std::__1::__function::__func<T>::operator() (function.h:311)
5   KreddyMobile                    0x203a77f5c         facebook::react::RuntimeScheduler_Modern::runEventLoopTick (RuntimeScheduler_Modern.cpp:340)
6   KreddyMobile                    0x203a77c30         facebook::react::RuntimeScheduler_Modern::runEventLoop (RuntimeScheduler_Modern.cpp:271)
7   KreddyMobile                    0x203a488cc         [inlined] std::__1::__function::__value_func<T>::operator()[abi:ne180100] (function.h:428)
8   KreddyMobile                    0x203a488cc         [inlined] std::__1::function<T>::operator() (function.h:981)
9   KreddyMobile                    0x203a488cc         [inlined] const::lambda::operator() (ReactInstance.cpp:83)
10  KreddyMobile                    0x203a488cc         [inlined] std::__1::__invoke[abi:ne180100]<T> (invoke.h:344)
11  KreddyMobile                    0x203a488cc         [inlined] std::__1::__invoke_void_return_wrapper<T>::__call[abi:ne180100]<T> (invoke.h:419)
12  KreddyMobile                    0x203a488cc         [inlined] std::__1::__function::__alloc_func<T>::operator()[abi:ne180100] (function.h:169)
13  KreddyMobile                    0x203a488cc         std::__1::__function::__func<T>::operator() (function.h:311)
14  KreddyMobile                    0x2038edbe8         [inlined] std::__1::__function::__value_func<T>::operator()[abi:ne180100] (function.h:428)
15  KreddyMobile                    0x2038edbe8         [inlined] std::__1::function<T>::operator() (function.h:981)
16  KreddyMobile                    0x2038edbe8         facebook::react::tryAndReturnError (RCTCxxUtils.mm:73)
17  KreddyMobile                    0x2038fc084         facebook::react::RCTMessageThread::tryFunc (RCTMessageThread.mm:68)
18  KreddyMobile                    0x2038fbe88         [inlined] std::__1::__function::__value_func<T>::operator()[abi:ne180100] (function.h:428)
19  KreddyMobile                    0x2038fbe88         [inlined] std::__1::function<T>::operator() (function.h:981)
20  KreddyMobile                    0x2038fbe88         facebook::react::RCTMessageThread::runAsync (RCTMessageThread.mm:44)
21  CoreFoundation                  0x31f0a884c         __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
22  CoreFoundation                  0x31f04b024         __CFRunLoopDoBlocks
23  CoreFoundation                  0x31f04c524         __CFRunLoopRun
24  CoreFoundation                  0x31f04dad8         CFRunLoopRunSpecific
25  KreddyMobile                    0x203a445a4         +[RCTJSThreadManager runRunLoop] (RCTJSThreadManager.mm:102)
26  Foundation                      0x31c9a3800         __NSThread__start__
27  libsystem_pthread.dylib         0x4339ab340         _pthread_start

MANDATORY Reproducer

N/A

Screenshots and Videos

No response

Activity

  1. react-native-bot commented on Sep 15, 2025

    @react-native-bot
    Collaborator

    Warning

    Missing reproducer: We could not detect a reproducible example in your issue report. Reproducers are mandatory and we can accept only one of those as a valid reproducer:


    You can read more about about it on our website: How to report a bug.

  2. emandres commented on Sep 16, 2025

    @emandres

    I'm getting the same error. Been throwing things at the wall for two weeks on this, so far. Interested to see what the RN team has to say.

  3. zeta92 commented on Sep 19, 2025

    @zeta92

    Hey I am getting the same error, I can't reproduce it but is happening to the 5% of our users

  4. abhayagrawal-fareye commented on Sep 24, 2025

    @abhayagrawal-fareye

    facebook::react::TelemetryController::pullTransaction(std::__1::function<void (facebook::react::MountingTransaction const&, facebook::react::SurfaceTelemetry const&)> const&, std::__1::function<void (facebook::react::MountingTransaction const&, facebook::react::SurfaceTelemetry const&)> const&, std::__1::function<void (facebook::react::MountingTransaction const&, facebook::react::SurfaceTelemetry const&)> const&) const
    EXC_BAD_ACCESS (KERN_INVALID_ADDRESS)

    Facing similar errors in react native 0.77.3

  5. Brma1048 commented on Sep 24, 2025

    @Brma1048

    also getting this error.
    React Native: 0.79.6
    New Arch: true

  6. ElieGhanimeh commented on Sep 25, 2025

    @ElieGhanimeh

    Also getting this error
    React Native: 0.80.0
    New Arch: true

    The app is being used by over 500 users and most probably more, only happening to a few.

  7. SanjayConverge commented on Sep 29, 2025

    @SanjayConverge

    Same issue after upgrading from v0.71.19 - v0.80.1 .
    It is happening to some random users on iOS.

    Crash logged in the Sentry :

    EXC_BAD_ACCESS: Exception 1, Code 1, Subcode 24 > KERN_INVALID_ADDRESS at 0x18 ; facebook::react::concreteComponentDescriptorConstructor<T>

  8. emandres commented on Sep 29, 2025

    @emandres

    So, I've been fighting these for the better part of a month now. Since we don't seem to be getting a response from the React Native team, I thought I'd share what I've learned.

    I was able to clean up a lot of these errors by making sure that the app doesn't try to update components after they've been removed from the screen. There are a lot of things to check for this, but offhand:

    • Make sure you clean up event listeners in your useEffect hooks;
    • Asynchronous code needs to check that the component is still mounted after each await
    • Make sure you cancel any setInterval calls on unmount; same goes for setTimeout

    I've managed to reduce the number of these errors quite a bit with the above tactics. We've narrowed down the majority of the remaining errors to something being thrown during the Expo Update process. It seems like the update downloads just fine, but once the app reboots to apply the update we get the error.

    Another thing to be aware of is that ErrorBoundary only works for exceptions that are thrown during the React component lifecycle. That means that most errors thrown from async code and event handlers don't get caught. I found recently that there is a way to set up a global error handler in React Native: ErrorUtils.setGlobalHandler. This change is still to be proven in production, so take it with a grain of salt.

    The only other bit of wisdom I can impart is to work on your app's observability. We were flying blind because we didn't have the DSYM files uploaded to Sentry, and any over-the-air updates we did after the initial native release weren't getting the source map files.

  9. ElieGhanimeh commented on Sep 30, 2025

    @ElieGhanimeh

    So, I've been fighting these for the better part of a month now. Since we don't seem to be getting a response from the React Native team, I thought I'd share what I've learned.

    I was able to clean up a lot of these errors by making sure that the app doesn't try to update components after they've been removed from the screen. There are a lot of things to check for this, but offhand:

    • Make sure you clean up event listeners in your useEffect hooks;
    • Asynchronous code needs to check that the component is still mounted after each await
    • Make sure you cancel any setInterval calls on unmount; same goes for setTimeout

    I've managed to reduce the number of these errors quite a bit with the above tactics. We've narrowed down the majority of the remaining errors to something being thrown during the Expo Update process. It seems like the update downloads just fine, but once the app reboots to apply the update we get the error.

    Another thing to be aware of is that ErrorBoundary only works for exceptions that are thrown during the React component lifecycle. That means that most errors thrown from async code and event handlers don't get caught. I found recently that there is a way to set up a global error handler in React Native: ErrorUtils.setGlobalHandler. This change is still to be proven in production, so take it with a grain of salt.

    The only other bit of wisdom I can impart is to work on your app's observability. We were flying blind because we didn't have the DSYM files uploaded to Sentry, and any over-the-air updates we did after the initial native release weren't getting the source map files.

    Thank you for sharing! I think it's happening for me because of a remote config check using Firebase, not entirely sure though. It's only happening on production as well after days of usage, I can't really replicate the error on my simulator to debug it furthermore which is kinda annoying.

  10. Brma1048 commented on Sep 30, 2025

    @Brma1048

    @cortinico is it possible that somebody of the react native team can take a look into this issue?

  11. cortinico commented on Sep 30, 2025

    @cortinico
    Contributor

    @cortinico is it possible that somebody of the react native team can take a look into this issue?

    Without a repro: highly unlikely

  12. 15 remaining items

  13. matinzd commented on Jan 12, 2026

    @matinzd
    ContributorAuthor

    I am on 79 right now :D There are some fixes related to EXC_BAD_ACCESS in the repo but not sure if it's related.

    UPDATE:

    Still happening with 0.81 too. We are currently sending 0.83 update to see if that fixes anything but I think we still need this to be released.

  14. hoangman1108 commented on Jan 17, 2026

    @hoangman1108

    "react-native": "0.81.5" get the same issue on some devices.

  15. taraskarpyn commented on Jan 19, 2026

    @taraskarpyn

    @matinzd some updates for this?
    Did you find something that causing this issue?

  16. ElieGhanimeh commented on Jan 19, 2026

    @ElieGhanimeh

    Upgraded to react-native 0.83.0, still having the same issue, i've been trying to figure it out since October, this is getting out of hand and frustrating.

  17. henryjrr1655 commented on Feb 5, 2026

    @henryjrr1655

    Also have been receiving these for a while and haven't been able to fix it.

    React  - 19.0.0  
    React Native - 0.79.2
    Expo - 53.0.6
    
    EXC_BAD_ACCESS
    Level: Fatal
    Exception 1, Code 1, Subcode 15024425151586640745 > KERN_INVALID_ADDRESS at 0xd0817b1292c42369.
    
  18. ElieGhanimeh commented on Feb 10, 2026

    @ElieGhanimeh

    Is anyone using SSO for the application? react-native-app-auth or something similar?

  19. Navipro70 commented on Apr 7, 2026

    @Navipro70

    The same unstable crashes

    "react-native": "0.83.2"
    "expo": "^55.0.4"
    
    Image
  20. Navipro70 commented on Apr 9, 2026

    @Navipro70

    As I understood those crashes aren't unstable

    Sentry showing EXC_BAD_ACCESS just for 95% of crashes and cannot determinate reason cause of leak of context. I think it's kind of related to #56265 and #55390 I've patched my RN version with the second PR, and will keep eye on crashes behaviour in new release

  21. matinzd commented on Apr 11, 2026

    @matinzd
    ContributorAuthor

    As I understood those crashes aren't unstable

    Sentry showing EXC_BAD_ACCESS just for 95% of crashes and cannot determinate reason cause of leak of context. I think it's kind of related to #56265 and #55390 I've patched my RN version with the second PR, and will keep eye on crashes behaviour in new release

    I sent a pick request for this to be merged into 0.83, and it has now been picked, but I am not sure when it will be released. I am using prebuilt core, which makes it hard to patch locally.

    I also believe this should fix the issue, as I am seeing many EXC_BAD_ACCESS errors in the performVoidMethodInvocation function.

  22. matinzd commented on Apr 17, 2026

    @matinzd
    ContributorAuthor

    A new 0.83 release is out from 0.83 stable branch.

    Image

    I am going to release a new version of the app. I will keep everyone posted.

  23. kirin72 commented on Aug 24, 2026

    @kirin72

    We hit the same crash family and traced a concrete lifetime bug in the code path. Sharing in case it helps unblock Needs: Repro.

    Environment: RN 0.81.5, React 19.1.0, Expo SDK 54, New Architecture (Fabric + Hermes), iOS 18.4–27.0, across 13 app releases and 9 device models. 59 crashes / 14 users over 34 days.

    Our stack is identical to yours down to runEventLoopTick, but the next frame differs — ours is updateRendering, yours is uiManagerDidFinishTransaction. I believe these are two sides of the same bug: one is the producer, the other the consumer of the same queue.

    11  facebook::react::RuntimeScheduler_Modern::runEventLoop
    12  facebook::react::RuntimeScheduler_Modern::runEventLoopTick
    13  facebook::react::RuntimeScheduler_Modern::updateRendering
    14  std::__1::__function::__func<T>::operator()
    

    The lifetime bug

    Scheduler captures a raw SchedulerDelegate* by value into a lambda queued on RuntimeScheduler, and nothing ever drains that queue when the delegate dies:

    // Scheduler.cpp:254 (same shape at :280 in uiManagerDidDispatchCommand)
    runtimeScheduler_->scheduleRenderingUpdate(
        surfaceId,
        [delegate = delegate_,                       // Scheduler.h:128 — raw pointer
         mountingCoordinator = std::move(mountingCoordinator)]() {
          delegate->schedulerShouldRenderTransactions(mountingCoordinator);
        });
    
    // RuntimeScheduler_Modern.cpp:193
    pendingRenderingUpdates_.push(renderingUpdate);
    
    // RuntimeScheduler_Modern.cpp:352-357 — consumed later, possibly much later
    while (!pendingRenderingUpdates_.empty()) {
      auto& pendingRenderingUpdate = pendingRenderingUpdates_.front();
      if (pendingRenderingUpdate != nullptr) {
        pendingRenderingUpdate();                    // dangling delegate here
      }
      pendingRenderingUpdates_.pop();
    }

    No teardown path drains this queue:

    Teardown What it does Drains queue?
    -[RCTScheduler dealloc] setDelegate(nullptr), destroys _delegateProxy (unique_ptr) no
    Scheduler::~Scheduler() setEventTimingDelegate(nullptr), uiManager_->setDelegate(nullptr) no
    RuntimeScheduler_Modern has no destructor at all no

    setDelegate(nullptr) only nulls Scheduler::delegate_. It cannot reach the copies already captured in queued lambdas. And RuntimeScheduler outlives Scheduler (held as a weak_ptr in the ContextContainer, tied to the JS runtime lifetime), so the queue survives its delegate.

    Worth noting: the two neighbouring delegates in the same file are protected — eventTimingDelegate_ is nulled by ~Scheduler(), and intersectionObserverDelegate_ by IntersectionObserverManager.cpp:157. Only the captured delegate_ is unguarded.

    Why this matches the symptom

    On iOS, -[RCTScheduler dealloc] follows RCTSurfacePresenter suspend (_scheduler = nil drops the last strong ref). One of its callers is _applicationWillTerminate, registered for UIApplicationWillTerminateNotification.

    That lines up with our data: all 18 sampled events have app.lifecycle: background as the last breadcrumb, and 4 of them fired 648s–10705s into background — i.e. when iOS reclaimed the suspended app.

    Fault addresses are high-entropy 64-bit values in all 18 events (0xce19b1971f8f0df3, 0x9a9b9f9163ca5e8b, 0x93e119d10a4844bf, …), never a small null-offset — consistent with reading a vtable pointer out of freed heap rather than a null deref.

    What we could not determine

    We have not confirmed this with a sanitizer. We built with ASan and ran the backgrounding flow many times without triggering it — expected in hindsight, since plain backgrounding never calls suspend (background is not terminate), and ASan's overhead shifts the race window anyway.

    So the open question is which of the three suspend callers fires in the ~1s-after-backgrounding cases (14 of our 18 events).

    This is analysis from reading 0.81.5 sources, not a verified repro. But if it holds, the fix direction would be either draining pendingRenderingUpdates_ on Scheduler teardown, or capturing the delegate through something that can be liveness-checked rather than a raw pointer.

  24. KingWangGH commented on Sep 3, 2026

    @KingWangGH

    react-native+0.77.3.patch
    I am using AI in version 0.77.3 to try to fix this issue, but I don't know how effective it will be。

  25. Brma1048 commented on Sep 14, 2026

    @Brma1048

    @matinzd any updates?

  26. gufao commented on Sep 18, 2026

    @gufao

    Adding instruction-level evidence for the uiManagerDidDispatchCommand half of this, which
    confirms @kirin72's lifetime analysis and pins down four things that were open above.

    Environment: RN 0.81.5, React 19.1.0, Expo SDK 54, New Architecture (Fabric + Hermes),
    iOS 18–26, App Store build. 237 fatal events over 3 months, still ongoing. It is our
    highest-volume crash by a factor of 2.5.


    1. You can symbolicate these without a dSYM

    This is probably the most immediately useful part for everyone here, because several comments
    mention flying blind.

    With RCT_USE_PREBUILT_RNCORE (Expo SDK 54 turns it on by default), the React.framework
    your app ships is Meta's prebuilt artifact, byte for byte. I checked the framework inside
    our App Store .xcarchive against the one CocoaPods had just downloaded from
    repo1.maven.org/.../react-native-artifacts/0.81.5/:

    UUID: 76FBCEE9-3517-30B3-9DC6-62C4E914E908 (arm64)   <- shipped binary
    UUID: 76FBCEE9-3517-30B3-9DC6-62C4E914E908 (arm64)   <- Maven artifact
    

    And that artifact is not stripped of local symbols (~19.7k local t symbols). So:

    atos -o React.xcframework/ios-arm64/React.framework/React -arch arm64 -l 0 <offset>
    #  offset = instruction_addr - image_addr, straight from the Sentry JSON

    resolves every React.framework frame, for anyone, with no dSYM and no archive. Worth
    validating first by running it on the frames your crash reporter already symbolicated from
    the device symtab — five of ours matched exactly, which is what proves you have the right
    binary.

    2. It is not producer/consumer — they are two different lambdas

    @kirin72 suggested uiManagerDidFinishTransaction and updateRendering are two sides of one
    queue. They are actually two separate capture sites, and updateRendering is the consumer
    of both:

    • Scheduler.cpp:254 — uiManagerDidFinishTransaction's lambda (the stack in the original
      report, and @taraskarpyn's).
    • Scheduler.cpp:280 — uiManagerDidDispatchCommand's lambda (ours).

    Symbolicating our top frame gives that second one literally:

    std::__1::__function::__func<
      facebook::react::Scheduler::uiManagerDidDispatchCommand(
          std::shared_ptr<const ShadowNode>&, const std::string&, const folly::dynamic&)::$_0,
      ...>::operator()()
    

    Both are consumed by RuntimeScheduler_Modern::updateRendering(). Same defect, two entry
    points.

    3. The faulting instruction

    The whole lambda body is four instructions (offsets are from the 0.81.5 arm64 artifact):

    1af9d8  ldr x8, [x0, #0x8]    ; x8 = delegate (2nd capture in the __func object)
    1af9dc  add x1, x0, #0x10     ; &shadowView
    1af9e0  add x2, x0, #0xf8     ; &commandName
    1af9e4  add x3, x0, #0x110    ; &args
    1af9e8  ldr x9, [x8]          ; x9 = vptr of *delegate
    1af9ec  ldr x4, [x9, #0x18]   ; <-- faults here
    1af9f0  mov x0, x8
    1af9f4  br  x4                ; <-- or here
    

    In our events ldr x9,[x8] succeeds — the delegate's memory is still mapped — but the
    first word is not a vtable pointer. relevant_address is exactly x9 + 0x18 every time.
    That is a freed-and-reused SchedulerDelegateProxy, not a null deref, which matches the
    high-entropy fault addresses @kirin72 reported.

    Our crash reporter had filed these as two separate issues, and the register state shows
    they are the same defect failing at adjacent instructions:

    fault pc top frame
    224 events ldr x4,[x9,#0x18] valid text address unsymbolicated __func::operator()
    13 events br x4 garbage updateRendering+156 (i.e. the lr)

    In the second group pc == x4 and x0 == x8, both of which only hold if execution reached
    1af9f0. In one of them the garbage branch target decodes to the ASCII __init, in another
    to sharedVa — reused heap holding string data. If your crash title contains an odd short
    string, that may be what it is
    ; it is worth checking before concluding it points at some
    other library.

    4. The trigger looks like TextInput blur-on-unmount

    commandName is captured at __func + 0xF8 and x2 points straight at it. "blur" is 4
    bytes, so libc++ keeps the characters inline (SSO) — meaning x2 is a register pointing at a
    printable string, which is exactly what sentry-cocoa's notable addresses pass picks up and
    puts in the title. Our titles read EXC_BAD_ACCESS: blur >, and x2 is the only register in
    the dump pointing at a printable string. Same in both issues.

    So the producing call is dispatchCommand(node, "blur", …). blur is one of the three Fabric
    commands on TextInput (['focus','blur','setTextAndSelection']), and RN itself dispatches
    it when a focused TextInput unmounts
    (TextInput.js:415-418).

    If your title has a different short string, that is your commandName, and it names the
    component that produced it.

    5. One data point against "it only happens on the way out"

    @kirin72 found app.lifecycle: background last in all 18 sampled events and pointed at
    _applicationWillTerminate. We see that too — but one of our events has
    app.in_foreground: true
    , with the user tapping a TextInput on a chat screen and the app
    dying about 2 seconds later. So at least one teardown path reaches this while the app is
    actively on screen. Which caller of suspend that is, we could not determine either.

    6. Why nobody has produced an RNTesterPlayground.js repro in 12 months

    The race needs both halves at once: a queued dispatchCommand (JS side, easy and
    deterministic — unmount a focused TextInput) and a scheduler teardown (native side,
    not reachable from JS). A JS-only playground can only ever produce one of the two. That may
    be worth taking into account before this is closed on Needs: Repro.

    7. Suggested direction

    Re-read the delegate at call time through something revocable, instead of copying the raw
    pointer at queue time. Minimal version, keeping delegate_ as-is for all the synchronous
    callers:

    --- a/ReactCommon/react/renderer/scheduler/Scheduler.h
    +++ b/ReactCommon/react/renderer/scheduler/Scheduler.h
    @@
    +#include <atomic>
     #include <memory>
     #include <mutex>
    @@
       SchedulerDelegate* delegate_;
    +  std::shared_ptr<std::atomic<SchedulerDelegate*>> sharedDelegate_{
    +      std::make_shared<std::atomic<SchedulerDelegate*>>(nullptr)};
    --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp
    +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp
    @@ Scheduler::Scheduler(
    -  delegate_ = delegate;
    +  setDelegate(delegate);      // single writer, keeps both in sync
    @@ Scheduler::~Scheduler() {
    +  sharedDelegate_->store(nullptr);
    @@ void Scheduler::setDelegate(SchedulerDelegate* delegate) {
       delegate_ = delegate;
    +  sharedDelegate_->store(delegate);
    @@ uiManagerDidFinishTransaction
    -          [delegate = delegate_,
    +          [sharedDelegate = sharedDelegate_,
                mountingCoordinator = std::move(mountingCoordinator)]() {
    -            delegate->schedulerShouldRenderTransactions(mountingCoordinator);
    +            if (auto* delegate = sharedDelegate->load()) {
    +              delegate->schedulerShouldRenderTransactions(mountingCoordinator);
    +            }
    @@ uiManagerDidDispatchCommand
    -        [delegate = delegate_,
    +        [sharedDelegate = sharedDelegate_,
              shadowView = std::move(shadowView), commandName, args]() {
    -          delegate->schedulerDidDispatchCommand(shadowView, commandName, args);
    +          if (auto* delegate = sharedDelegate->load()) {
    +            delegate->schedulerDidDispatchCommand(shadowView, commandName, args);
    +          }

    Being honest about what this is: it narrows the window from a whole event-loop tick to
    three instructions. It is not a proof of race-freedom — draining
    pendingRenderingUpdates_ on Scheduler teardown would be stronger, if the ownership allows
    it. main still has both capture sites unchanged today.

    8. On patching locally — @matinzd is right, and here is the knob

    With prebuilt core, patching node_modules does nothing: react/renderer/scheduler/*.cpp
    is not in the Pods project at all (the scheduler subspec ships headers only), so the
    patched source is never compiled. We verified this end to end before believing it.

    On Expo the switch is expo-build-properties → ios.buildReactNativeFromSource: true, which
    does put Scheduler.cpp back into the Pods project and drops React-Core-prebuilt. Fair
    warning: on our toolchain that build then fails in fmt itself
    (format-inl.h:59: call to consteval function … is not a constant expression), before it ever
    reaches RN code — so "just patch it locally" currently costs two yaks, not one.

  27. gufao commented on Sep 18, 2026

    @gufao

    Correcting my own comment above: I said main still has both capture sites unchanged, which
    is true, but I missed that this was already fixed once and then deliberately removed. For
    anyone in this thread deciding whether to upgrade, the version map matters:

    RN delegateInvalidated_ guard effective by default?
    ≤ 0.85.x not present —
    0.86.0 – 0.87.1 present, gated by enableSchedulerDelegateInvalidation no — ReactNativeFeatureFlagsDefaults.h returns false
    0.88.0-rc.1 / main removed, flag deleted —

    The part worth surfacing here is that #58138's own summary already names the window this
    thread is reporting:

    One window is genuinely left open, and is now pinned by a new death test: a delegate dropped
    and destroyed with no error involved never reaches handleTaskError, so nothing clears the
    queue and the drained lambda dereferences freed memory. Closing that properly needs a
    runtime-scheduler-level shutdown signal rather than a per-Scheduler token.

    That is exactly our crash: no JS error is involved, the RCTScheduler is simply torn down, so
    handleTaskError never runs and nothing drains the queue. clearQueues() has exactly one
    caller in both 0.87.1 and main, and it is inside handleTaskError.

    So, concretely, for people asking whether upgrading helps:

    • 0.86.x / 0.87.x contain the guard in the shipped (including prebuilt) binary, but it is
      off. ReactNativeFeatureFlags::override(...) is public RN_EXPORT, so it can be switched on
      from app startup code without patching or building RN from source — which also sidesteps the
      prebuilt-core problem @matinzd raised.
    • 0.88+ removes that option again.

    Given the death test exists, the remaining gap seems to be known and scoped rather than
    unreproduced — a runtime-scheduler shutdown signal. Would it be reasonable to track that as the
    follow-up to #58138 rather than leaving this issue on Needs: Repro?

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: AttentionIssues where the author has responded to feedback.Needs: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions