-
Notifications
You must be signed in to change notification settings - Fork 24.3k
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
Support background timers on iOS via NSTimer #21211
Conversation
Not really. Audio apps are something of an exception, as iOS allows you to continue executing code in background. Almost all apps will be suspended within a few moments of being put to background. I'd assume if you're playing in Xcode with background modes, you kind of know what you're doing. It's still worth pointing it out in the documentation that background behavior of timers may not be strictly relied upon |
@jamesreggio can you rebase past bc8d052 and 5068dfc? I'm interested in seeing if this will pass the TimingModuleTest in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK with making this change, but open to hearing any concerns related to the background behavior James highlighted here.
I can merge once tests are passing.
@jamesreggio Do you know how this compares to android? Also will the timers fire at all if the app does not declare any background modes? |
2a46955
to
be12c32
Compare
Thanks for the thoughtful replies, everybody. Upon further inspection, you need to be actively using an approved Background Mode for this to provide any value whatsoever. (Otherwise, the app will be terminated within a couple of seconds of reaching the background, as expected.) However, this change remains important for apps that legitimately use a Background Mode (like our audio app), because without it, any callbacks supplied to I agree with @radex that there's no need to make this behavior opt-in, given the necessity of opting in to a Background Mode within Xcode. This merely just causes React Native to 'do the right thing' when you're in the background. @janicduplessis — for apps without any Background Modes, this change will cause timers to fire in the couple of seconds between moving to the background and being suspended, which is still helpful if you're doing anything with a short In terms of Android, allow me to preface by saying that I'm working off of recollections of Android's timing module from several months ago. However, if I recall correctly, Android requires the use of external libraries to support long-lived or background timers — hence the common YellowBox warning to that effect. In a sense, Android's timer implementation (via Choreographer.postFrameCallback) contains only the CADisplayLink half of the iOS implementation. If Android was modified to support long-lived timers via @hramos — I rebased against |
@jamesreggio they do look unrelated. No need to rebase for now, since these failures are showing up on master as well. |
Sounds great, Héctor. I think this is ready to go, then?
…On Mon, Sep 24, 2018, 5:44 PM Héctor Ramos ***@***.***> wrote:
@jamesreggio <https://github.com/jamesreggio> they do look unrelated. No
need to rebase for now, since these failures are showing up on master as
well.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#21211 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAyLve7HFTJeDIrON5dFwV0O94aTTLOaks5ueVJZgaJpZM4Ww-MP>
.
|
(@hramos — sorry, didn't remember your handle to @-mention you from my email reply.) I think this is ready to go. Let me know if there is anything else you need on my end. |
Hi again — this is ready to go. @hramos, do you mind initiating Facebook import, or letting me know whether any unresolved objections remain? I'd like to field them before I lose familiarity with these systems. |
Hi @jamesreggio and @hramos - is any update available on the status of this PR and if it can be merged for an upcoming release? We are using React Native to develop an app which makes heavy use of dynamic background audio, and this patch is a huge quality-of-life improvement for our customers - so much so that we are manually applying this patch in our XCode / iOS buildflow as a workaround in the meantime. Thanks for your work on this! |
It's ready — just waiting on FB. Glad to hear it's working for you, though! |
Hi @jamesreggio - we're in final testing for our release including this patch (applied manually), and it appears to be causing hard crashes in at least a couple of edge cases. I'm not especially familiar with I've included excerpts from two different crash dumps below. They both point to line 278, in I know this code wasn't directly changed by your patch, but it seems closely related. Do you happen to have any thoughts / ideas for us to try? We'll continue investigating on our side as well. Thanks! Crash excerpt 1:
Crash excerpt 2:
|
Quick update - we managed to eliminate the most common / egregious crashes by changing
Again, my knowledge of this system is quite limited so if anyone here has thoughts on the issue / a better long-term fix please do let me know. It seems like this might not be the only thread-safety issue that might result from this change, so I'll keep this thread posted on anything else we find once we're live to a larger scale. |
We've found one additional crash since launch which appears to be fixed by also Thanks! |
Sorry for the delay. Let's workout whether the changes by @cojo need to be included in this PR, and then I'll import this for further review from our team. |
Importing to put it on my queue. I can always re-import to get any additional commits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hramos has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
Thanks @hramos - if it's helpful, here is the "full patch" file we're applying in our build process internally here at this point to resolve all the crashes we have found so far: react-native-timers.patch - I'm not 100% sure how to integrate these changes with this existing PR but I'm happy to do so. |
What's the status of this PR? |
Looks like I should probably incorporate @cojo's feedback. I had other, far simpler PRs open that weren't receiving any motion post-import from FB (until today), so I didn't prioritize making changes here just to have them sit. Now that I see you're on it, @cpojer, I'll incorporate @cojo's patch, smoke test, and push. |
Just to follow up, I can now confirm on our side that my latest patch (the one on Dec 11 2018) has solved all remaining crashes we're aware of on production (for our app, at least). Thanks again @jamesreggio for getting this set up! |
Could you add examples on the usage of this feature to RNTester as well? |
@jamesreggio hey! Did you have a chance to look at my last comment about adding a manual test case for this? |
@jamesreggio @cojo just pinging one more time. Could either of you update this PR so we can actually merge it? |
@cpojer If @jamesreggio is unavailable to update the PR, I can set aside some time to apply my changes directly; however, this PR appears to have been opened from a personal fork of his. My understanding is I would have to PR my changes to his fork first before it would be applied to this PR. Is that correct? If so, if he's out of town, that might not work either. Is there another way for me to apply my changes to this PR or would it be better to open my own PR with the full changeset and reference this PR from there? Thanks! |
Your own PR should be fine. |
@hramos @cpojer |
Summary: This PR is a follow-up to #21211 by request of hramos to incorporate some additional crash fixes / synchronization edge cases found in our production testing. What follows is largely a copy of the original PR description from #21211 - see that PR for original discussion thread as well as context on why this replacement PR was needed. This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background. Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS. The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background. This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use. It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks. It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background. The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use `setTimeout` and `setInterval` without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground. Pull Request resolved: #23674 Differential Revision: D14621326 Pulled By: shergin fbshipit-source-id: c76e060ad2c662c140d7d2f4fb5aaa7094032515
Summary: This sync includes the following changes: - **[f7cdc8936](facebook/react@f7cdc8936 )**: Also turn off enableSyncDefaultUpdates in RN test renderer ([#21293](facebook/react#21293)) //<Ricky>// - **[4c9eb2af1](facebook/react@4c9eb2af1 )**: Add dynamic flags to React Native ([#21291](facebook/react#21291)) //<Ricky>// - **[9eddfbf5a](facebook/react@9eddfbf5a )**: [Fizz] Two More Fixes ([#21288](facebook/react#21288)) //<Sebastian Markbåge>// - **[11b07597e](facebook/react@11b07597e )**: Fix classes ([#21283](facebook/react#21283)) //<Sebastian Markbåge>// - **[96d00b9bb](facebook/react@96d00b9bb )**: [Fizz] Random Fixes ([#21277](facebook/react#21277)) //<Sebastian Markbåge>// - **[81ef53953](facebook/react@81ef53953 )**: Always insert a dummy node with an ID into fallbacks ([#21272](facebook/react#21272)) //<Sebastian Markbåge>// - **[a4a940d7a](facebook/react@a4a940d7a )**: [Fizz] Add unsupported Portal/Scope components ([#21261](facebook/react#21261)) //<Sebastian Markbåge>// - **[f4d7a0f1e](facebook/react@f4d7a0f1e )**: Implement useOpaqueIdentifier ([#21260](facebook/react#21260)) //<Sebastian Markbåge>// - **[dde875dfb](facebook/react@dde875dfb )**: [Fizz] Implement Hooks ([#21257](facebook/react#21257)) //<Sebastian Markbåge>// - **[a597c2f5d](facebook/react@a597c2f5d )**: [Fizz] Fix reentrancy bug ([#21270](facebook/react#21270)) //<Sebastian Markbåge>// - **[15e779d92](facebook/react@15e779d92 )**: Reconciler should inject its own version into DevTools hook ([#21268](facebook/react#21268)) //<Brian Vaughn>// - **[4f76a28c9](facebook/react@4f76a28c9 )**: [Fizz] Implement New Context ([#21255](facebook/react#21255)) //<Sebastian Markbåge>// - **[82ef450e0](facebook/react@82ef450e0 )**: remove obsolete SharedArrayBuffer ESLint config ([#21259](facebook/react#21259)) //<Henry Q. Dineen>// - **[dbadfa2c3](facebook/react@dbadfa2c3 )**: [Fizz] Classes Follow Up ([#21253](facebook/react#21253)) //<Sebastian Markbåge>// - **[686b635b7](facebook/react@686b635b7 )**: Prevent reading canonical property of null ([#21242](facebook/react#21242)) //<Joshua Gross>// - **[bb88ce95a](facebook/react@bb88ce95a )**: Bugfix: Don't rely on `finishedLanes` for passive effects ([#21233](facebook/react#21233)) //<Andrew Clark>// - **[343710c92](facebook/react@343710c92 )**: [Fizz] Fragments and Iterable support ([#21228](facebook/react#21228)) //<Sebastian Markbåge>// - **[933880b45](facebook/react@933880b45 )**: Make time-slicing opt-in ([#21072](facebook/react#21072)) //<Ricky>// - **[b0407b55f](facebook/react@b0407b55f )**: Support more empty types ([#21225](facebook/react#21225)) //<Sebastian Markbåge>// - **[39713716a](facebook/react@39713716a )**: Merge isObject branches ([#21226](facebook/react#21226)) //<Sebastian Markbåge>// - **[8a4a59c72](facebook/react@8a4a59c72 )**: Remove textarea special case from child fiber ([#21222](facebook/react#21222)) //<Sebastian Markbåge>// - **[dc108b0f5](facebook/react@dc108b0f5 )**: Track which fibers scheduled the current render work ([#15658](facebook/react#15658)) //<Brian Vaughn>// - **[6ea749170](facebook/react@6ea749170 )**: Fix typo in comment ([#21214](facebook/react#21214)) //<inokawa>// - **[b38ac13f9](facebook/react@b38ac13f9 )**: DevTools: Add post-commit hook ([#21183](facebook/react#21183)) //<Brian Vaughn>// - **[b943aeba8](facebook/react@b943aeba8 )**: Fix: Passive effect updates are never sync ([#21215](facebook/react#21215)) //<Andrew Clark>// - **[d389c54d1](facebook/react@d389c54d1 )**: Offscreen: Use JS stack to track hidden/unhidden subtree state ([#21211](facebook/react#21211)) //<Brian Vaughn>// - **[c486dc1a4](facebook/react@c486dc1a4 )**: Remove unnecessary processUpdateQueue ([#21199](facebook/react#21199)) //<Sebastian Markbåge>// - **[cf45a623a](facebook/react@cf45a623a )**: [Fizz] Implement Classes ([#21200](facebook/react#21200)) //<Sebastian Markbåge>// - **[75c616554](facebook/react@75c616554 )**: Include actual type of `Profiler#id` on type mismatch ([#20306](facebook/react#20306)) //<Sebastian Silbermann>// - **[1214b302e](facebook/react@1214b302e )**: test: Fix "couldn't locate all inline snapshots" ([#21205](facebook/react#21205)) //<Sebastian Silbermann>// - **[1a02d2792](facebook/react@1a02d2792 )**: style: delete unused isHost check ([#21203](facebook/react#21203)) //<wangao>// - **[782f689ca](facebook/react@782f689ca )**: Don't double invoke getDerivedStateFromProps for module pattern ([#21193](facebook/react#21193)) //<Sebastian Markbåge>// - **[e90c76a65](facebook/react@e90c76a65 )**: Revert "Offscreen: Use JS stack to track hidden/unhidden subtree state" ([#21194](facebook/react#21194)) //<Brian Vaughn>// - **[1f8583de8](facebook/react@1f8583de8 )**: Offscreen: Use JS stack to track hidden/unhidden subtree state ([#21192](facebook/react#21192)) //<Brian Vaughn>// - **[ad6e6ec7b](facebook/react@ad6e6ec7b )**: [Fizz] Prepare Recursive Loop for More Types ([#21186](facebook/react#21186)) //<Sebastian Markbåge>// - **[172e89b4b](facebook/react@172e89b4b )**: Reland Remove redundant initial of isArray ([#21188](facebook/react#21188)) //<Sebastian Markbåge>// - **[7c1ba2b57](facebook/react@7c1ba2b57 )**: Proposed new Suspense layout effect semantics ([#21079](facebook/react#21079)) //<Brian Vaughn>// - **[316aa3686](facebook/react@316aa3686 )**: [Scheduler] Fix de-opt caused by out-of-bounds access ([#21147](facebook/react#21147)) //<Andrey Marchenko>// - **[b4f119cdf](facebook/react@b4f119cdf )**: Revert "Remove redundant initial of isArray ([#21163](facebook/react#21163))" //<Sebastian Markbage>// - **[c03197063](facebook/react@c03197063 )**: Revert "apply prettier ([#21165](facebook/react#21165))" //<Sebastian Markbage>// - **[94fd1214d](facebook/react@94fd1214d )**: apply prettier ([#21165](facebook/react#21165)) //<Behnam Mohammadi>// - **[b130a0f5c](facebook/react@b130a0f5c )**: Remove redundant initial of isArray ([#21163](facebook/react#21163)) //<Behnam Mohammadi>// - **[2c9fef32d](facebook/react@2c9fef32d )**: Remove redundant initial of hasOwnProperty ([#21134](facebook/react#21134)) //<Behnam Mohammadi>// - **[1cf9978d8](facebook/react@1cf9978d8 )**: Don't pass internals to callbacks ([#21161](facebook/react#21161)) //<Sebastian Markbåge>// - **[b9e4c10e9](facebook/react@b9e4c10e9 )**: [Fizz] Implement all the DOM attributes and special cases ([#21153](facebook/react#21153)) //<Sebastian Markbåge>// - **[f8ef4ff57](facebook/react@f8ef4ff57 )**: Flush discrete passive effects before paint ([#21150](facebook/react#21150)) //<Andrew Clark>// - **[b48b38af6](facebook/react@b48b38af6 )**: Support nesting of startTransition and flushSync (alt) ([#21149](facebook/react#21149)) //<Sebastian Markbåge>// Changelog: [General][Changed] - React Native sync for revisions c9aab1c...f7cdc89 jest_e2e[run_all_tests] Reviewed By: rickhanlonii Differential Revision: D27740113 fbshipit-source-id: 6e27204d78e3e16ed205170006cb97c0d6bfa957
This PR is a minimalistic change to RCTTiming that causes it to switch exclusively to NSTimer (i.e., the 'sleep timer') in order to continue triggering timers when the app has moved to the background.
Motivation:
Many people have expressed a desire for background timer support on iOS. (See #1282, #167, and #16493). In our app — a podcast/audio player — we use background timers to ensure that we never lose track of the user's playback position, should the app crash or be terminated by the OS.
The RCTTimer module uses a RN-managed CADisplayLink if the next requested timer is less than a second away; otherwise, it switches to an NSTimer (which is refers to as a 'sleep timer' in source). The RN-managed CADisplayLink is always disabled when the app goes to the background (and thus cannot be used); however, the NSTimer will still issue its callbacks in the background.
This PR adds a flag to track whether the app is in the background, and if so, all timers are routed through NSTimer until the app returns to the foreground. @vishnevskiy at Discord opened a similar PR (#16493) that implements a drop-in for CADisplayLink which falls back to NSTimer, but I decided to incorporate the background-NSTimer logic directly into RCTTimer, since NSTimer is already in use.
It's worth noting that the background NSTimer may not fire as often as requested — it may give the appearance of lagging depending upon your app's priority in the background. For our audio app, NSTimer fires exactly on schedule if there's an open AVAudioSession and audio is playing; if audio is not playing, it fires about half as often as requested, which is still adequate for networking polling and other tasks.It's worth noting that background timers only function as long as an app is actually running in the background. Apple offers a variety of Background Modes (which can be toggled in the Capabilities section of the target inspector in Xcode), and the app will need to be legitimately using one of these modes in order for this change to provide any value — otherwise it will be terminated within a couple of seconds of moving to the background.
The good thing about this change is that for apps that do perform essential computation in support of their Background Mode, they can now use
setTimeout
andsetInterval
without problem — whereas in the past, neither would ever trigger their callback until the app returns to the foreground.Open Question:
Despite the demand for this timer behavior, it's not clear whether this should be specifically opt-in. There's nothing specifically breaking about this change, but an app that abuses timers may lead to increased battery consumption if they now fire in the background.On the other hand, if we were to make this an opt-in feature, it's not clear how to do it other than to expose a top-level function to toggle it (and that feels hacky and undiscoverable). RN's timers are based upon standard JS functions which cannot have their parameters modified to take additional flags.This is no longer a concern due to the fact that the app must have a valid Background Mode, which is already a multi-step opt-in process.
Test Plan:
We've been using this patch in our production app without issue since January 2018.
I've tested on a real iPhone 6 running iOS 10 and a real iPhone 8 running iOS 11. It also functions as expected on various simulators.
To demonstrate the difference, here is a before and after video.
Release Notes:
[IOS] [ENHANCEMENT] [Timers] - Support background timers on iOS