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

[iOS] JS Timers (setTimeout) don't fire when app is launched in background #38711

Closed
mikelehen opened this issue Aug 1, 2023 · 8 comments
Closed
Labels
Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. Needs: Triage 🔍 Newer Patch Available Platform: iOS iOS applications.

Comments

@mikelehen
Copy link

mikelehen commented Aug 1, 2023

Description

Summary:
Timers (setTimeout, etc.) don't run when iOS launches your React Native app in the background, because RCTTiming initializes _inBackground=NO.

More detail: #23674 added support for iOS timers to run when the app is in the background, but I believe I'm running into a case not covered by this code. In particular I'm implementing "background updates" for my app. If the app is not running when the background update arrives (e.g. after a reboot, or presumably if iOS has killed the app to free resources), iOS will launch the app from scratch in the background (as opposed to the more normal background case of being launched in the foreground and then later transitioning to the background which is handled by appDidMoveToBackground), and in this case timers (setTimeout, etc.) do not work.

It looks like if I change RCTTiming to initialize _inBackground=YES here, then my background code works as expected. But this isn't a good fix because in the normal foreground app launch case _inBackground will still be YES so it will end up using the "background" timers logic until you transition to background and then back to foreground.

I think the correct fix would be something like:

_inBackground = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;

But that can only be called from the main thread. So you'd need to dispatch to the main thread, and I'm not sure how to do that safely without introducing race conditions.

Vague repro:
Sorry, to repro this you need to send specific push notifications from a backend server to the app. And you also need to potentially wait ~3 hours for iOS to run your background code. So I can't provide a direct repro.

  1. Per the background updates docs, I've enabled the "Remote notifications" background mode on my app so iOS will run my app in the background for remote notification background updates.
  2. I reboot my phone to make sure my app is not running in the background (I am assuming this could also happen if iOS has terminated the app to free resources, but rebooting is the best way I've found to test this).
  3. I send a push notification from the backend to my app with "content-available=1" in order to trigger the background update code.
  4. I wait ~3 hours for iOS to decide to run the background sync code (I don't know what heuristics iOS uses, but after a reboot it seems to be ~3 hours before it triggers).
  5. iOS launches my app in the background and runs my application(_:didReceiveRemoteNotification:fetchCompletionHandler:) delegate method.
  6. I use that delegate to fire an RCTEventEmitter event that I listen for in JavaScript, in order to run my background update code.

Result: My JavaScript code handles that event and successfully runs in the background, but any calls to setTimeout() or setInterval() do not work (my callbacks never run).

React Native Version

0.72.0

Output of npx react-native info

info Fetching system and libraries information...
System:
OS: macOS 13.4
CPU: (10) arm64 Apple M1 Max
Memory: 72.59 MB / 32.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 18.16.0
path: ~/.nvm/versions/node/v18.16.0/bin/node
Yarn:
version: 1.22.19
path: ~/.yarn/bin/yarn
npm:
version: 9.5.1
path: ~/.nvm/versions/node/v18.16.0/bin/npm
Watchman: Not Found
Managers:
CocoaPods: Not Found
SDKs:
iOS SDK:
Platforms:
- DriverKit 22.4
- iOS 16.4
- macOS 13.3
- tvOS 16.4
- watchOS 9.4
Android SDK: Not Found
IDEs:
Android Studio: 2022.2 AI-222.4459.24.2221.9862592
Xcode:
version: 14.3.1/14E300c
path: /usr/bin/xcodebuild
Languages:
Java:
version: 20.0.1
path: /usr/bin/javac
Ruby:
version: 3.2.2
path: /opt/homebrew/opt/ruby/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.72.1
wanted: ^0.72.0
react-native-macos: Not Found
npmGlobalPackages:
"react-native": Not Found
Android:
hermesEnabled: Not found
newArchEnabled: Not found
iOS:
hermesEnabled: Not found
newArchEnabled: Not found

Steps to reproduce

Sorry, I am not providing specific repro steps since it would rely on backend code sending push notifications to the app. My repro steps also rely on rebooting the device and waiting 3 hours, which isn't a very friendly repro. So I'm providing this bug in the hope that it is useful to somebody, but I understand it is not directly actionable. I understand if that means this bug gets closed. At least it will be in the searchable history of the repro.

Snack, screenshot, or link to a repository

Unavailable, sorry. See above.

@github-actions github-actions bot added the Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. label Aug 1, 2023
@github-actions
Copy link

github-actions bot commented Aug 1, 2023

⚠️ Missing Reproducible Example
ℹ️ We could not detect a reproducible example in your issue report. Please provide either:
  • If your bug is UI related: a Snack
  • If your bug is build/update related: use our Reproducer Template. A reproducer needs to be in a GitHub repository under your username.

@github-actions
Copy link

github-actions bot commented Aug 1, 2023

⚠️ Newer Version of React Native is Available!
ℹ️ You are on a supported minor version, but it looks like there's a newer patch available - 0.72.3. Please upgrade to the highest patch for your minor or latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If it does not repro, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the most recent releases.

@mikelehen
Copy link
Author

In my case, I was able to work around it by adding this code to my app (e.g. in AppViewController.loadView()):

        let timing = bridge.loadModule(for: RCTTiming.self)
        if UIApplication.shared.applicationState == UIApplication.State.background {
            if timing.responds(to: Selector("appDidMoveToBackground")) {
                timing.perform(Selector("appDidMoveToBackground"))
            }
        }

@zhongwuzw
Copy link
Contributor

@mikelehen Hi, I made a PR to fix this, please see #39347 , Does it work for you?

@mikelehen
Copy link
Author

@zhongwuzw Thanks for making the PR! Yes, it does work, though I am wondering if there's a potential race condition now that it's running async. Left a comment on the PR. Either fix (yours or the one I suggested) seems to resolve my use case though. Thanks for working on this!

Titozzz pushed a commit that referenced this issue Sep 11, 2023
…39347)

Summary:
Fixes #38711

## Changelog:

[IOS] [FIXED] - Fix timer background state when App is launched from background

Pull Request resolved: #39347

Test Plan: Please see #38711

Reviewed By: cipolleschi

Differential Revision: D49101979

Pulled By: dmytrorykun

fbshipit-source-id: e25b182539f39e4465fa40e51288d88c68967b31
@mikelehen
Copy link
Author

Thanks so much @zhongwuzw, @cipolleschi, and @Titozzz! 🙏

@cheehieu
Copy link

cheehieu commented Nov 9, 2023

@mikelehen, @zhongwuzw Did you also run into this issue on Android?

I am trying to implement remote background pushes to wake up my app when it is force quit. I have it mostly working using react-native-firebase and this merged PR, but I noticed that a setTimeout() is not being called in my background handler on Android (via HeadlessJS task). It is working fine on iOS, but I realize the behavior is slightly different as it launches the entire app into the background.

@mikelehen
Copy link
Author

@cheehieu Sorry, my react native app is iOS-only so I can't speak to any Android issues. Good luck!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs: Repro This issue could be improved with a clear list of steps to reproduce the issue. Needs: Triage 🔍 Newer Patch Available Platform: iOS iOS applications.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants