Which thread to call uploadTask from URLSession

Hi, I would like to know if it is safe to call the uploadTask from URLSession from the main thread ?

We've a user who is reporting repeated crashes at startup, here is the stack we see:

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Termination Reason: FRONTBOARD 2343432205 
<RBSTerminateContext| domain:10 code:0x8BADF00D explanation:scene-update watchdog transgression: app<com.appspot.myApp(E7590BB1-722C-491D-9199-F867DE4B880A)>:2212 exhausted real (wall clock) time allowance of 10.00 seconds
ProcessVisibility: Background
ProcessState: Running
WatchdogEvent: scene-update
WatchdogVisibility: Background
WatchdogCPUStatistics: (
"Elapsed total CPU time (seconds): 21.260 (user 10.230, system 11.030), 35% CPU",
"Elapsed application CPU time (seconds): 0.006, 0% CPU"
) reportType:CrashLog maxTerminationResistance:Interactive>

Triggered by Thread:  0

Thread 0 name:   Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	       0x1def7a688 mach_msg2_trap + 8
1   libsystem_kernel.dylib        	       0x1def7dd98 mach_msg2_internal + 80
2   libsystem_kernel.dylib        	       0x1def7dcb0 mach_msg_overwrite + 424
3   libsystem_kernel.dylib        	       0x1def7dafc mach_msg + 24
4   libdispatch.dylib             	       0x1968d8f14 _dispatch_mach_send_and_wait_for_reply + 544
5   libdispatch.dylib             	       0x1968d92b4 dispatch_mach_send_with_result_and_wait_for_reply + 60
6   libxpc.dylib                  	       0x21714a930 xpc_connection_send_message_with_reply_sync + 256
7   Foundation                    	       0x18d80a3ac __NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ + 16
8   Foundation                    	       0x18d806b14 -[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:] + 2160
9   CoreFoundation                	       0x18eb868dc ___forwarding___ + 1004
10  CoreFoundation                	       0x18eb86430 _CF_forwarding_prep_0 + 96
11  CFNetwork                     	       0x1900c71e0 -[__NSURLBackgroundSession setupBackgroundSession] + 800
12  CFNetwork                     	       0x1900b3e80 -[__NSURLBackgroundSession initWithConfiguration:delegate:delegateQueue:delegateDispatchQueue:] + 552
13  CFNetwork                     	       0x1900b4784 +[NSURLSession _sessionWithConfiguration:delegate:delegateQueue:delegateDispatchQueue:] + 1496
14  MyApp                        	       0x1054210b4 CombineBgXferRepository.session.getter (in MyApp) (CombineBgXferRepository.swift:62) + 7966900
15  MyApp                        	       0x105422fa4 CombineBgXferRepository.startUploadTask(fileURL:request:) (in MyApp) (CombineBgXferRepository.swift:310) + 7974820

If it is ok to call this uploadTask from the main thread, does this crash indicate a problem with the operating system? Are there scenarios where the background upload service does not respond to requests?

Apple has historically not shipped APIs that are blocking -- if so, they are marked as async or include some kind of callback. As a result, I believe that this indicates some kind of problem with the operating system and potentially a bug that should be reported to Apple...?

We've a user who is reporting repeated crashes at startup, here is the stack we see:

Please post the full crash log using these instructions. From long experience, all but the simplest of crash logs can't really be diagnosed without the larger context of the full log. I could come up with a variety of explanation for that stack, all of which would be completely invalidated by activity on other threads.

Hi, I would like to know if it is safe to call the uploadTask from URLSession from the main thread ?

Yes, I would generally consider it safe, as it shouldn't be blocking for any significant period of time.

If it is ok to call this uploadTask from the main thread, does this crash indicate a problem with the operating system?

There's no way to know that given the information that stack trace. More specifically, looking ONLY at that crash stack:

-The assumption here is that you blocked here*:

11  CFNetwork                     	       0x1900c71e0 -[__NSURLBackgroundSession setupBackgroundSession] + 800

*Everything above that point are the more general functions the system uses for XPC communication, which means they're SO broadly used that you can basically treat them as "bug free".

That assumption could very well be true, but you don't actually know that. The crash log you're looking at was generated at the moment the system terminated your app and ONLY shows you what your app was doing at that moment, NOT what is was doing "before" that. If an app spends 4.9s blocked in foo() and then blocks in bar() for 0.1s, every termination log will blame "bar()", even though "foo()" is in fact the problem.

-You don't know WHY you blocked there at all. If the system blocked then that COULD be an OS problem, but if YOU blocked there then the problem is "you" (see below).

That leads to here:

Are there scenarios where the background upload service does not respond to requests?

No, at least not in the straightforward senses of "if you do this, then this crash happens". The background URL session is very heavily used and that means "straightforward" bugs are relatively rare, simply because they cause so many problems.

However, putting on my Psychic Debugging Hat™, the first question I would ask is what, if any, analytics services your app includes. The XPC call in that stack shouldn't have blocked for any significant period of time, so IF it blocked, one of two things was happening:

  1. And issue on the system side meant that the daemon (nsurlsessiond) was unable to respond for a "long" ("seconds") time.

  2. Your app prevented the thread from executing.

Behind the scenes, many analytics libraries collect data by suspending the thread(s), capturing a stack trace, and then resuming the thread. That process is FAR more dangerous than it sounds and if/when something goes wrong, this is the kind of failure it often generates.

__
Kevin Elliott
DTS Engineering, CoreOS/Hardware

Hi, I'm attaching a full crash stack trace. There is indeed a thread that might seem suspicious.

Thread 3 name:  com.google.firebase.crashlytics.MachExceptionServer
Thread 3:

Hi, I'm attaching a full crash stack trace. There is indeed a thread that might seem suspicious.

Actually, this is isn't unusual:

Thread 3 name:  com.google.firebase.crashlytics.MachExceptionServer
Thread 3:
0   libsystem_kernel.dylib        	       0x1def7a688 mach_msg2_trap + 8
1   libsystem_kernel.dylib        	       0x1def7dd98 mach_msg2_internal + 80
2   libsystem_kernel.dylib        	       0x1def7dcb0 mach_msg_overwrite + 424
3   libsystem_kernel.dylib        	       0x1def7dafc mach_msg + 24
4   MyApp                        	       0x1054d39e8 FIRCLSMachExceptionServer (in MyApp) (FIRCLSMachException.c:168) + 8698344
5   libsystem_pthread.dylib       	       0x2170f537c _pthread_start (in libsystem_pthread.dylib) + 136 (/Library/Caches/com.apple.xbs/Sources/libpthread/src/pthread.c:931)
6   libsystem_pthread.dylib       	       0x2170f0494 thread_start (in libsystem_pthread.dylib) + 8

Crashlytics uses that to receive the mach exceptions that it uses to drive the crash collection process. I'm happy to strenuously argue against in process crash collection, but I don't think it's a factor.

As another minor note, a thread sequence like this always concerns me:

Thread 1 name:   Dispatch queue: com.google.fira.worker
...
Thread 2 name:  com.apple.uikit.eventfetch-thread

In a "basic" app configuration, Thread 1 will/should ALWAYS be "com.apple.uikit.eventfetch-thread", as that's the first thread the system creates in UIApplicationMain. Having a different thread 1 requires that "something" be doing work (in this case, GCD work) before UIApplicationMain started executing, which always makes me a bit uncomfortable. However, I don't have any reason to directly implicate that thread.

That leads to the main thread, which could be normal or could be very concerning. The place I would start here is by adding an "abort()" call just before this line:

58  MyApp                        	       0x105a21ad4 closure #2 in ApplicationMediator.onDidFinishLaunchingEnd() (in MyApp) (ApplicationMediator.swift:228) + 14260948

It took 148 frames to get to that method which... seems like a lot. It's possible that this is totally normal (in which case the problem is elsewhere), but if it is unusual then that might be a strong indicator of what's going wrong.

As a similar technique, I would also add an abort() call somewhere in here:

14  MyApp                        	       0x1054210b4 CombineBgXferRepository.session.getter (in MyApp) (CombineBgXferRepository.swift:62) + 7966900
15  MyApp                        	       0x105422fa4 CombineBgXferRepository.startUploadTask(fileURL:request:) (in MyApp) (CombineBgXferRepository.swift:310) + 7974820

Then use the time data of the two logs to try and assess how long the "normal" case actually takes. You're app stalled for 10s:

:2212 exhausted real (wall clock) time allowance of 10.00 seconds

So comparing those to number starst to give you a baseline for how much time actually "disappeared"- there a huge difference when "normal" is 8s vs 0.1s.

Related to that point, what is that this code actually "doing"? From the context your obviously initiating some kind of upload, but what's managing and, more importantly, constraining that activity? You started by asking if it was safe to call uploadTask on the main thread but the safety of the individual calls doesn't matter if you're looping 100s/1000s of times. The size of the loop matters here to and, from overall context, you could very well be looking between these two point:

15  MyApp                        	       0x105422fa4 CombineBgXferRepository.startUploadTask(fileURL:request:) (in MyApp) (CombineBgXferRepository.swift:310) + 7974820
...
58  MyApp                        	       0x105a21ad4 closure #2 in ApplicationMediator.onDidFinishLaunchingEnd() (in MyApp) (ApplicationMediator.swift:228) + 14260948

...which opens up a lot of opportunity for wasted time and "hidden" activity.

This also relates back to what you said here:

We've a user who is reporting repeated crashes at startup, here is the stack we see:

If this is specifically "a user", that is only 1 person is crashing like this, then that generally means that they're doing "something" very different than other users. What exactly that is a don't know, but one of the obvious possibilities is simply that they've got a lot more <something> than anyone else, which then exposes an issue no one else has hit before.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Which thread to call uploadTask from URLSession
 
 
Q