Timer issues whilst backgrounded in iOS 10.3

Since the update to 10.3, there appears to be some regression in scheduling timers (both using NSTimer and GCD) to run whilst the application is backgrounded. We're using VoIP/audio background modes (our app does not feature in the App Store and is only used internally).


In previous versions, the timers fire within our set tolerances, but in iOS 10.3 the timers start to drift to the point where it's firing way too late (almost 1 minute more on a 30 second timer). Strangely, the timers seem to fire reliably whilst the device is attached to my machine.


I've seen a similar post here from February that seems to mirror the same behaviour.


Could this be down to the migration to APFS, or some sort of internal "Android Doze"-esque feature?


Thanks,

Dave

Replies

Timers are based of Mach absolute time, which stops counting when the CPU sleeps. This can cause all sorts of problems. It’s generally not a good idea to leave a timer running when you app is eligible for suspension (the OS can suspend your app without sleeping the CPU, but if your app is ineligible for suspension then, obviously, the CPU can’t sleep). I recommend that you invalidate any timers when you become eligible for suspension and re-install them when that state lifts.

Note While doing this you may also want to take into account wall clock changes, if that’s relevant to your timers.

For a normal app this is easy (invalidate on moving to the background, re-install on coming to the foreground) but it gets quite tricky for an app that runs in the background. The OS does not tell you when it’s about to suspend your app, nor does it tell you when it’s resumed after a suspension. One approach you can use here is to centralise all of your handling of ‘resume in the background’ event sources (like VoIP sockets, signifcant location change notifications, background fetches, and so on) so that you can track when you become eligible and ineligible for suspension. This is also a good place to handle your starting and ending of UIApplication background tasks.

btw This is not new behaviour; I’m surprised you didn’t see it on earlier systems.

Strangely, the timers seem to fire reliably whilst the device is attached to my machine.

It’s likely that the CPU never sleeps in that case, and thus you never see the above-mentioned discrepancy.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi,


Appreciate the response, thanks!


The main reason why I thought that there might have been a change in iOS 10.3 was that we're using the timers to periodically upload data at a given interval, regardless of whether the app is in the background or foreground. We noticed that after the update all the phones running iOS 10.3 submitted data regularly for a short period of time before then starting to drift significantly. The devices running previous versions of iOS do not suffer from the same issue and submit data at regular intervals until the application is closed.


I also double checked that our application was beginning/ending background tasks regularly to rule out the application being suspended.


Starting and invalidating timers (as you would normally!) when the app changes state is undesirable because of the need for the regular uploads.


I appreciate that this is an unusual use-case but It seems that everything is as it should be. You don’t think this could be related to the new file system update somehow?


Thanks again,

Dave

I think we have similar issue in our VoiP application. App have a running backgrounding task and a timer with 5s time interval. When timer fires app is dumping current time and UIApplication.backgroundTimeRemaining value. Since iOS 10.3 when screen is locked (this is important) we can see the following in dump:


19:45:13.431 -> KA time remaining: 179

19:45:18.485 -> KA time remaining: 174

19:45:25.553 -> KA time remaining: 168

19:45:34.139 -> KA time remaining: 159

19:45:39.087 -> KA time remaining: 154

19:49:18.593 -> KA time remaining: 149

19:49:23.543 -> KA time remaining: 144

19:49:28.562 -> KA time remaining: 139

19:52:05.653 applicationWillEnterForeground


You can see that between KA timer values 154 and 149 app was actually put into suspended mode and ~3.5 minutes was passed! And seems app continues to run only after I unlocked device.


There was no such issue in iOS 10.2 and older versions.

You don’t think this could be related to the new file system update somehow?

That seems most unlikely, but 10.3 was a relatively big release (especially as far as dot releases go) so it’s well possible that some other change is affecting this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I'm seeing this too, athough the behavior seems to manifest itself only when the lock screen is up; if the app is sent to background using the home button but the lock screen isn't engaged, it doesn't happen.

I actually found what is that. See https://developer.apple.com/videos/play/wwdc2013/204/ first few minutes. Backgrounding task does not guarantee that 180 seconds you have in background to do job are continuous after screen locked. They introduced this in iOS7, but seems like it only started to happen from iOS10.3

We're seeing the same behaviour in our app and I don't understand how this is expected. We're also using checking UIApplication.backgroundTimeRemaining (e.g. 180 secs) and then create a background task before setting the timer to 20secs (via beginBackgroundTaskWithExpirationHandler). @eskimo: if the timer period is shorter than the remaining background time this should prevent the app from being suspended, correct?

if the timer period is shorter than the remaining background time this should prevent the app from being suspended, correct?

That’s what I’d expect. Alas, some other engineer has been tracking this for DTS and thus I’m not really up to speed on the issue.

Can you isolate this into a small test project?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

WWDC runs Mon, 5 Jun through to Fri, 9 Jun. During that time all of DTS will be at the conference, helping folks out face-to-face. http://developer.apple.com/wwdc/

My application usage patterns like you, VoIP & Background Music, also found the same problem, finally you solve the problem?

We're seeing similar strange behaviour in iOS 10.3.


We use timers to start and stop location tracking in order to periodically check the user's location in the background but in iOS 10.3 our timers do not appear to be firing correctly and the app is being terminated after the regular three minutes of allowed background time has elapsed. Is there any extra information about anything that might have changed in 10.3 to affect this?

[I’m responding at the bottom of this thread because it’s hard to track conversations that occur in the middle.]

Adam Knight wrote:

Is there any extra information about anything that might have changed in 10.3 to affect this?

Not from me, alas.

I should stress that every time I’ve looked at problems like this they were caused by the developer making invalid assumptions about NSTimer. I posted my general advice on this topic back on 5 Apr 2017.

… in iOS 10.3 our timers do not appear to be firing correctly and the app is being terminated after the regular three minutes of allowed background time has elapsed.

I’m confused by this. In general any code running in the background should be using a UIApplication background task to ensure that it stays running. When creating a background task you have to pass in an expiry handler so that the system can ‘call in’ your background task and thus suspend the app early. The only way to get killed here is for the expiry handler to not respond promptly. Is your expiry handler dependent on an NSTimer? Presumably not, in which case I can’t see how any NSTimer issue could cause you to be killed in this way.

Regardless, to move forward here you need to get serious about:

  • Reducing the problem to the minimum set of factors that still reproduce it

  • Add logging to each step

You can then look at the log to see what’s actually happening with your app on 10.3.x.

IMPORTANT When you test this, test it with your app run from the home screen (not Xcode) and with the device on battery power. Xcode’s debugger radically changes background execution behaviour, so you never want to test background code that way. Likewise, mains power can cause iOS to behave quite differently, so it’s best to test your app on battery, as it’s going to be used by your users.

With iOS 10’s new logging architecture you can test while disconnected and then recover the log after the fact via sysdiagnose. There are instructions for getting a sysdiagnose on our Bug Reporting > Profiles and Logs page.

Finally, I should stress that I totally believe that there’s some change in behaviour in iOS 10.3 that’s causing all of these problems. The question here is whether the new behaviour is within the bounds laid out by the documentation for the APIs in question. If it isn’t, that’s definitely bugworthy. OTOH, if it is then you should feel free to file a binary compatibility bug but the best way forward is to update your code.

Regardless, any progress here (be that a bug report with a workaround, or just a fix to your code) is going to need a proper analysis of what’s going wrong, hence my logging suggestions.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Myself having issues using GCD as a timer .. i thought too that the timers were more accurate when attched to my machine .. which they clearly are .. BUT :

Then i noticed it wasnt just when attached to my computer but if they are being charged .period .. ie like in a wall charger even..

Then my timers run pretty much at the times indicated.. which befuddlles me even more :-(

Seem related to me ..


https://forums.developer.apple.com/thread/91466