How to prevent crash due exceeding background time?

I am using background URLSessions to retrieve data when my app refreshes in background. I got this error on my log:


Termination Description: SPRINGBOARD, CSLHandleBackgroundURLSessionAction watchdog transgression: francisaugusto.MyApp.watchkitapp.watchkitextension exhausted real (wall clock) time allowance of 15.00 seconds | <FBExtensionProcess: 0x96915d0; TMyApp Wat (francisaugusto.MyApp.watchkitapp.watchkitextension); pid: 292; typeID: com.apple.watchkit; host: Carousel:31> Elapsed total CPU time (seconds): 1.060 (user 1.060, system 0.000), 4% CPU | Elapsed application CPU time (seconds): 0.002, 0% CPU, lastUpdate 2018-02-06 14:00:28 +0000

Triggered by Thread: 0


Anyway I could prevent this to happen?

I have the same, exact problem. Really wish someone could offer some advice or figure it out. To make things worse, this happens regularily after about 24-40 hours of the app working perfectly. I've tried adding


backgroundConfigObject.timeoutIntervalForRequest = 6.0


in the configuration for my background session, thinking that the default timeout for the URLSession of 60 seconds was too long and could sometimes exceed 15 seconds with poor network conditions (although, the notion that something like that should crash the app just seems wrong). After changing it to 6, it does seem like the app takes longer to crash in the background, but it still happens.


Next thing I'm about to try is forcing the main thread for the complications update. That seems like the kind of thing that could take long to complete only once in a blue moon if not on a main thread, and I've had the app running for 2+ days with no crashes when I've just had handlel(nil) in the getCurrentTimelineEntryForComplication method.


*edit Forcing the main thread for the complication update methods didn't help. App still crashed, same as before, maybe even quicker than before.

Since it takes a day or two to try and verify anything on this problem (no luck so far with anything I tried), francisaugusto, can you tell me if you're accessing the extension delegate from your complication controller at any point? In my early tests, it looked like the app could work indefinitely for as long as the complication didn't need to access the extension delegate (or extension delegate to access the complication controller). I had a setup where I would just show the time of the latest refresh on the complication, and I'd initiate the complication server refresh on the extension delegate, without setting any chanel of communication between the extension delegate and complication controller.


Can you also tell me are you forcing the main thread execution onto anything background tasks related in your extension delegate methods? I was all along forcing the task completion onto the main thread, as per some suggestion I've read somewhere, but now I'm running a test where I've eliminated all main thread forcing on anything, to see if that changes things at all.


Lastly, have you been running any tests on watchOS 4.3 beta? I'm waiting to exhaust all the possible tests on latest official 4.2.2 OS, before I update to beta. I really think this problem might be watchOS related. Wouldn't suprise me in the least seeing how many weird thing they had happening in watchOS over the years.

@Marconelly,


I update my complications on the Interface Controller, but called from an Extensions Delegate function. I do execute some things on the main thread, especially to synchronize the multiple URLSessionDownloadTasks that I fire on background - I need to wait all of them (2) in order to update my interface. I do call the location manager to update my location, but that's done on an ApplicationBackgroundRefresh, not on the URLSessionBackgroundRefresh.


And nope, no beta here. I'm going to try to limit the timeout as you did to see if it improves. The good thing is that the app relaunches itself in the background if a background refresh was scheduled, so the crash is not as bad as it could be. Still bad, though.

Interestingly enough, I have the exact same thing going on. Two URLs that I download in the background, and can't update the complication before both are finished. What I do however, is fire them both off without specifying that they need to go on the main thread, and then in the downloadTask:didFinishDownloadingToURL method, I check the completed download's original request URL. This way I know which of the two download tasks has completed, and when both are completed (which I track using two booleans that I set to true when completed and then false on download restart), I proceed with complication update. This way I don't need to care in which orders downloads complete, and I don't have them on main thread.


My next test (if the current one fails) will actually be to have these NSURLsession completion handlers on main thread, by specifying session's delegateQueue as NSOperationQueue mainQueue. Note that specifying this doesn't actually run the download task itself on the main thread (I think background task downloads are never on main thread) but it forces the completion handler code to be executed on main thread.

I follow the same logic, but I do it slightly different:


Instead of two booleans (I did that before), I created an int that I increase atomically when a task is completed (I identify my tasks with a description). I increase this int on the main thread, and on my `didSet` code, I fire up my UI update if that int == numberOfTasksThatINeed. On the same `didSet`, I set my background tasks as completed.


Something that took me a while to understand is that, when I fire up the tasks, I imediately set my app refresh task as completed. That was giving me headaches before because I was seting it as complete before it finished doing what it had to do.

>Something that took me a while to understand is that, when I fire up the tasks, I imediately set my app refresh task as completed.

>That was giving me headaches before because I was seting it as complete before it finished doing what it had to do.

This whole backgeound task / handle in watchOS is like some kind of weird puzzle put out by Apple for developers to collectively solve, with how little is truly understood about it. Apple's own example given here is wrong: https://developer.apple.com/library/content/samplecode/WatchBackgroundRefresh/Listings/WatchBackgroundRefresh_WatchKit_Extension_MainInterfaceController_swift.html

(If you listen to the WWDC talk about this, they say you're supposed to store the background Download Session Task into a variable, and complete it only when the downloads are completed - which is the correct way to do it, and it's not done in this sample code). I so wish they haven't obsoleted the old getNextRequestedUpdateDateWithHandler code for complication update. It was rock solid, reliable and so simple to use.


In any case, my test with removing all the main thread forcing has also resulted in the app crashing the same way after about 30 hours. One thing I noticed however is that I may have a problem with the fact that I handle the download task failures with doing the DownloadTask cancel for both tasks. When I intentionally produced a 404 download failure for one of the tasks, I've onserved that this cancel one time didn't do anything (becuase the other task finished regardless) and the other time it sent the whole process into some kind of a tailspin where a ton of repeated schedules and failures continued piling on, before it settled down. The app didn't crash, but what happened was clearly wrong. In the test I'm running now, I'm no longer doing any download tasks cancelling or any micromanaging of what happens when anything fails. I just have the task completion code in the URLSessionDidFinishEventsForBackgroundURLSession method, and tha's where I schedule next background refresh as well. As far as I can tell, this method fires regardless if the download tasks have completed successfully or not.


Also, I was seeing the error described here: https://stackoverflow.com/questions/46464660/wkrefreshbackgroundtask-cleanupstorage-error-attempting-to-reach-file

I don't know if that error is harmful in any way, but doing something along the lines what was suggested there, got rid of the error. I did this in the handle method, which I think is a better idea than what was suggested there:


for (WKRefreshBackgroundTask *task in backgroundTasks) {
  userInfoAccess = task.userInfo;


I created the userInfoAccess variable on the class level, and then just assign that task userInfo value to it repeatedly.

I do things sligtly different: I complete the tasks on the `didDownloadFinishTo:` and also on the `if error != nil`on the `didFinishWithError` method, as, if one of the tasks don't finish correctly, I want to start all over again, reesceduling the URLSession again. Now it's working for 48 hours without a crash.

I was debating whether I should do that, but in debugging, it does seem like the URLSessionDidFinishEventsForBackgroundURLSession is called no matter if downloads are completed successfully or not. It is called only once, when both downloads are finished (as it should). Fingers crossed, but I think that now I'm relying just on that instead of trying to cancel the tasks if one of them errors out, my app has also been running for 40+ hours with no issue. Try testing the app background operation by forcing errors onto it. Make it so that one of the URLs becomes invalid after the first successful download, and see what happens. In another test, shorten the timeoutIntervalForRequest to be below what is realistically possible for the download to complete, and see how the app behaves when the task times out. I think in my case at least, problem was coming from an improper handling of the tasks that finished with an error.

Hiya! Can I just confirm what actually fixed this? Was it changing the `WKURLSessionRefreshBackgroundTask` to only complete in `URLSessionDidFinishEventsForBackgroundURLSession`? Or did you also store the original `WKApplicationRefreshBackgroundTask` which you used to spawn the `NSURLSession`s and also only complete that once they had finished? Do you fire of your NSURLSessions immediately as well? Or at a date later than the current time?

How to prevent crash due exceeding background time?
 
 
Q