Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async JS "stacking up" when MainActivity is in the background #50327

Open
descorp opened this issue Mar 27, 2025 · 13 comments
Open

Async JS "stacking up" when MainActivity is in the background #50327

descorp opened this issue Mar 27, 2025 · 13 comments
Labels
Needs: Attention Issues where the author has responded to feedback. Needs: Version Info

Comments

@descorp
Copy link

descorp commented Mar 27, 2025

Description

Hi everyone,
I'm encountering a really strange issue and hoping someone here might have some insight.

Here's the problem: In any new React Native app generated using @react-native-community/cli, when a secondary Activity is presented (and the main Activity is in the background), asynchronous JavaScript code seems to stop functioning correctly. It appears to "stack up" and only executes after the secondary Activity is dismissed.

For context, I'm developing a React Native package that has worked without this issue for the past two years. My local test app also works fine, and many users are using it successfully on modern React Native versions (I know of at least one on 0.76.7). This issue seems specific to freshly created apps.

I've created a minimal repo to reproduce the problem: https://github.com/descorp/RNAlertTest.

Interestingly, one user reported that turning off the "new Architecture" resolved this "thread freezing" problem for them, but I haven't been able to reproduce this fix locally.
My suspicion is that something has changed in the project generation process with @react-native-community/cli compared to the older npx react-native init, but I'm not sure what it could be.

Has anyone else experienced anything similar or have any ideas on what might be causing this? Any help would be greatly appreciated!

Steps to reproduce

  1. Clone https://github.com/descorp/RNAlertTest
  2. Run yarn && yarn android
  3. Press button to open dialog
  4. Press "Send event" on dialog and observe new messages console log
  5. Press "Send event async" on dialog and observe no new messages console log
  6. Dismiss dialog
  7. Observe debug console will all "stacked" async messages

Abstract:

  1. Initiate new project
  2. Add native module that presents activity above MainActivity (if there is a faster way, use it 😅).
  3. Open app and call new activity
  4. Call any async code (e.x. await sleep(100) or fetch('www.google.com'))
  5. Observe async code executed only after MainActivity on foreground

React Native Version

0.78.2

Affected Platforms

Runtime - Android

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

System:
  OS: macOS 15.3.2
  CPU: (16) arm64 Apple M4 Max
  Memory: 185.73 MB / 64.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.11.0
    path: /usr/local/adyen/nodejs/bin/node
  Yarn: Not Found
  npm:
    version: 11.0.0-pre.0
    path: /usr/local/adyen/npm/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.14.3
    path: /Users/vladimir/.local/share/gem/ruby/3.2.0/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.1
      - iOS 18.1
      - macOS 15.1
      - tvOS 18.1
      - visionOS 2.1
      - watchOS 11.1
  Android SDK:
    API Levels:
      - "33"
      - "34"
      - "35"
    Build Tools:
      - 30.0.3
      - 33.0.1
      - 34.0.0
      - 35.0.0
      - 35.0.0
      - 36.0.0
    System Images:
      - android-34 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: Not Found
  Xcode:
    version: 16.1/16B40
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 21.0.6
    path: /usr/bin/javac
  Ruby:
    version: 3.2.2
    path: /usr/local/adyen/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.74.7
    wanted: 0.74.7
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false

Stacktrace or Logs

LOG  AppState active
 LOG  
 LOG  Timer works before async
 LOG  Custom activity started
 LOG  AppState background
 LOG  Event sync
 LOG  Event sync
 LOG  AppState active
 LOG  Timer works after async
 LOG  Event async
 LOG  Event async

Reproducer

https://github.com/descorp/RNAlertTest

Screenshots and Videos

Image

@react-native-bot
Copy link
Collaborator

Warning

Could not parse version: We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.76.2.

@react-native-bot
Copy link
Collaborator

Warning

Could not parse version: We could not find or parse the version number of React Native in your issue report. Please use the template, and report your version including major, minor, and patch numbers - e.g. 0.76.2.

@cipolleschi
Copy link
Contributor

cipolleschi commented Mar 28, 2025

Hi @descorp thanks for the issue.

I highly doubt that the problem is related to the react-native-community/cli. The old react-native init was forwarding the initialization to react-native-community/cli under the hoods.

It is more likely that we changed something in the execution model and we "pause" the JS thread if we see that the activity is not active. I need to ask around as I don't have context about it.

Perhaps @cortinico or @rubennorte knows more about it.

Does it happens on Android only or also on iOS?

@descorp
Copy link
Author

descorp commented Mar 28, 2025

Does it happens on Android only or also on iOS?

Only on Android.
On iOS we are using the root view controller for presentation. To draw an analogy, using a Fragment instead of an Activity works as expected since MainActivity is in the foreground and async JS code not "stacking".

Before you ask, rewriting the native library too use Fragments instead of Activity is not a feasible option 😅

It is more likely that we changed something in the execution model and we "pause" the JS thread if we see that the activity is not active.

I have noticed similar behaviour on Android when app is dismisses (which make sense). Could be that at some point App's lifecycle was changed to MainActivity lifecycle f?

@github-actions github-actions bot added Needs: Attention Issues where the author has responded to feedback. and removed Needs: Author Feedback labels Mar 28, 2025
@cipolleschi
Copy link
Contributor

It could be. I don't have a lot of details on the Android side, unfortunately. @cortinico can you have a look at this?
I was asking internally and they told me that we always intended to pause JS execution when the Activity hosting React Native is not active, but it looks like that it was not implemented properly in the Old Arch, while it is in the New Arch... and now it's braking your library which was relying on that behavior... 😅

@descorp
Copy link
Author

descorp commented Mar 31, 2025

Thanks @cipolleschi

that it was not implemented properly in the Old Arch, while it is in the New Arch...

What grinds my gears is the fact, that disabling New Arch does not resolve it for me locally, and yet not stoping some library consumers with latest SDK to use it..

we always intended to pause JS execution when the Activity hosting

I understand the intent.
Important question - will this eventually be backward compatible or I should start looking at some "background JS runner" solution?

@cipolleschi
Copy link
Contributor

I don't have an answer right now, unfortunately. I think we have to discuss this more thoroughly internally, to understand what will be the right execution model for this use case, and that's not a decision a can make myself. I'll keep this issue updated as soon as I learn more about this.

@descorp
Copy link
Author

descorp commented Apr 2, 2025

Thanks!

we always intended to pause JS execution when the Activity hosting

Just to emphasise, sync JS code works perfectly on background (you can see that on my reproducer repo). Async code is the problem.

I am not a JS core expert, but my assumption is that something is off with the way how Tasks are scheduled on event loop while Activity on a background.

@lovegaoshi
Copy link

i remember seeing something similar when there was an effort to make headlessJsTaskService to be newarch compatible. this PR ended up solving it. i dunno how practical it is to mess with the new timer manager. perhaps adding a persisting headlessJsTaskService will ensure the timer not getting paused
and be a quick patch with proper clean up (RNTP works fine this way).

I've noticed setState also "stacks up" when MainAcitivty is backgrounded with new arch. I have no real solution other than pausing it all together.

@descorp
Copy link
Author

descorp commented Apr 9, 2025

Thanks for the tip @lovegaoshi
I also see this issue that seems related.

As far as I understand, problem is the setTimeout it is not ticking while on background because of HeadlessJsTaskContext limitation ?


Again, what grinds my gears is the fact, that setting newArchEnabled=false does not resolve it. Issue persist on 0.72 and 0.78 alike with and without newArchEnabled.

@lovegaoshi
Copy link

rather than a limitation, I see all of this working as intended - jstimer's onHostPause is functioning as expected and paused all async execution I'm presuming, unless there are active headlessJsTasks. sync js code executes just fine. I dont see this is a new vs old arch thing either. perhaps you can find something by diffing your working test app with a RN template at the time, it shouldnt work to begin with

unfortunately i dont have any experience on this without involving headlessJsTaskService. the other pointer I have is this seemingly setState "stacking" issue only with the new arch, but since u mentioned neither arch work, i doubt this is a real lead.

@descorp
Copy link
Author

descorp commented Apr 11, 2025

Okay, I've investigated further and believe I've identified the underlying causes:

Following behaviour reproduced on blank new 0.78.2 app:

  1. setTimer() do not work while on background for both "old" and "new arch".
    I shoot myself in a foot assuming that a simple async/await with setTimeout would be a reliable way to "reproduce"
    asynchronous behavior. Thanks @lovegaoshi for highlighting this area!
  2. new Promise(resolve => ... ) only work on background in a "bridge" mode (bridgelessEnabled = false or newArchEnabled=false)

This observation aligns with why the majority of users haven't experienced issues (likely using older RN versions or configurations) and why switching to the "old architecture" resolves the problem for those who do.


@cipolleschi does this behavior align with the React Native community's vision for background task execution, or should either of these points be considered for future improvement or clarification?
(in other words "bug or feature?")

@cipolleschi
Copy link
Contributor

I really have to defer this to @cortinico as he has more experience on Android than me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs: Attention Issues where the author has responded to feedback. Needs: Version Info
Projects
None yet
Development

No branches or pull requests

4 participants