Reputation: 5698
So my app currently can end up in a state where a network request is made, and another of the same request can be made while the first request is still waiting for the response
At least I think. And with that logic, it looks like Thread 11 spins up first, and then Thread 13. Then Thread 13 looks like it's waiting because Thread 11 is still waiting for a response
Thread 11 has: _dispatch_barrier_sync_f_invoke
and Thread 13 has: _dispatch_barrier_sync_f_slow
Thread 11 Crashed:
0 libsystem_kernel.dylib 0x00007fffab4d1dda __pthread_kill + 10
1 libsystem_c.dylib 0x00007fffab437440 abort + 129
2 CrashReporter 0x000000010f28d851 uncaught_exception_handler (PLCrashReporter.m:365)
3 CoreFoundation 0x00007fff963d7e29 __handleUncaughtException + 745
4 libobjc.A.dylib 0x00007fffaaac9b85 _ZL15_objc_terminatev + 94
5 libc++abi.dylib 0x00007fffa9fbdd69 _ZSt11__terminatePFvvE + 8
6 libc++abi.dylib 0x00007fffa9fbdde3 _ZSt9terminatev + 51
7 libobjc.A.dylib 0x00007fffaaac998e objc_terminate + 9
8 libdispatch.dylib 0x00007fffab36d13c _dispatch_client_callout + 28
9 libdispatch.dylib --->0x00007fffab36dd62 _dispatch_barrier_sync_f_invoke + 83
10 Snagit 0x000000010e1a5d68 -[AFURLSessionManager dataTaskWithRequest:completionHandler:] (AFURLSessionManager.m:664)
11 Snagit 0x000000010e19083e -[AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:success:failure:] (AFHTTPSessionManager.m:243)
12 Snagit 0x000000010e18fb0d -[AFHTTPSessionManager GET:parameters:success:failure:] (AFHTTPSessionManager.m:112)
13 Snagit 0x000000010e1d2f29 __51-[TSCAccountHTTPSession GET:parameters:completion:]_block_invoke (TSCAccountHTTPSession.m:317)
14 Snagit 0x000000010e1d464f __58-[TSCAccountHTTPSession performNetworkRequest:completion:]_block_invoke_2 (TSCAccountHTTPSession.m:495)
15 libdispatch.dylib 0x00007fffab375f5f _dispatch_call_block_and_release + 12
16 libdispatch.dylib 0x00007fffab36d128 _dispatch_client_callout + 8
17 libdispatch.dylib 0x00007fffab37c2ce _dispatch_queue_override_invoke + 743
18 libdispatch.dylib 0x00007fffab36eee0 _dispatch_root_queue_drain + 476
19 libdispatch.dylib 0x00007fffab36ecb7 _dispatch_worker_thread3 + 99
20 libsystem_pthread.dylib 0x00007fffab5b9746 _pthread_wqthread + 1299
21 libsystem_pthread.dylib 0x00007fffab5b9221 start_wqthread + 13
Thread 13:
0 libsystem_kernel.dylib 0x00007fffab4d23b6 __ulock_wait + 10
1 libdispatch.dylib 0x00007fffab385c6e _dispatch_thread_event_wait_slow + 85
2 libdispatch.dylib --->0x00007fffab3785ea _dispatch_barrier_sync_f_slow + 402
3 Snagit 0x000000010e1a5d68 -[AFURLSessionManager dataTaskWithRequest:completionHandler:] (AFURLSessionManager.m:664)
4 Snagit 0x000000010e19083e -[AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:success:failure:] (AFHTTPSessionManager.m:243)
5 Snagit 0x000000010e18fb0d -[AFHTTPSessionManager GET:parameters:success:failure:] (AFHTTPSessionManager.m:112)
6 Snagit 0x000000010e1d2f29 __51-[TSCAccountHTTPSession GET:parameters:completion:]_block_invoke (TSCAccountHTTPSession.m:317)
7 Snagit 0x000000010e1d464f __58-[TSCAccountHTTPSession performNetworkRequest:completion:]_block_invoke_2 (TSCAccountHTTPSession.m:495)
8 libdispatch.dylib 0x00007fffab375f5f _dispatch_call_block_and_release + 12
9 libdispatch.dylib 0x00007fffab36d128 _dispatch_client_callout + 8
10 libdispatch.dylib 0x00007fffab37c2ce _dispatch_queue_override_invoke + 743
11 libdispatch.dylib 0x00007fffab36eee0 _dispatch_root_queue_drain + 476
12 libdispatch.dylib 0x00007fffab36ecb7 _dispatch_worker_thread3 + 99
13 libsystem_pthread.dylib 0x00007fffab5b9746 _pthread_wqthread + 1299
14 libsystem_pthread.dylib 0x00007fffab5b9221 start_wqthread + 13
And here's the AFNetworking code that has the dispatch_sync
:
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
__block NSURLSessionDataTask *dataTask = nil;
dispatch_sync(
url_session_manager_creation_queue(), ^{
dataTask = [self.session dataTaskWithRequest:request];
}
);
[self addDelegateForDataTask:dataTask completionHandler:completionHandler];
return dataTask;
}
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
Upvotes: 3
Views: 1664
Reputation: 2248
I hope the following does answer your question. However, if you have an error in your program that you are trying to debug, and it is the fact, that the same request is being made twice, then this is not the code that would contain the error.
GCD
uses the so called barriers to synchronize the blocks on a dispatch queue, take a look at the documentation here: Dispatch and dispatch_barrier_async
A barrier ensure mutual exclusiveness of the barrier block on the queue, i.e. it executes all alone by itself.
A dispatch barrier allows you to create a synchronization point within a concurrent dispatch queue. When it encounters a barrier, a concurrent queue delays the execution of the barrier block (or any further blocks) until all blocks submitted before the barrier finish executing. At that point, the barrier block executes by itself. Upon completion, the queue resumes its normal execution behavior.
If the queue is serial, or global concurrent one, then:
From dispatch_barrier_sync: If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_sync function.
In fact dispatch_sync
delegates to dispatch_barrier_sync_*
and the call stack would look something like this:
// some inline functions omitted here
_dispatch_barrier_sync_f_invoke
_dispatch_barrier_sync_f
dispatch_sync_f
dispatch_sync
Now, if along the way the runtime cannot acquire the barrier for the current thread for an another block, then it takes the slow
road. This sets up all the synchronization for you and the caller thread waits. On the event
that the queue is once again free, then the thread continues and the next block is executed.
Basically it is the same call stack BUT with a detour along the way, with some lock (semaphore), which then leads back to _dispatch_barrier_sync_f_invoke
:
// again, some inline functions omitted here
_dispatch_barrier_sync_f_invoke
_dispatch_thread_event_wait
_dispatch_barrier_sync_f_slow
// here _dispatch_queue_try_acquire_barrier_sync fails...
_dispatch_barrier_sync_f
dispatch_sync_f
dispatch_sync
If you want to learn more about GCD
, then the best place to understand the internals is its source code repository on GitHub
Upvotes: 7