29

I'm using the Kiwi testing framework to test an authentication method in my app. The test freezes at a call to dispatch_sync which looks like this:

dispatch_queue_t main = dispatch_get_main_queue();
dispatch_sync(main, ^
                  {
                      [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationAuthenticationSuccess object:nil userInfo:ret];
                  });

I'd like to know why it freezes there, if anyone has any hints.

jscs
  • 63,694
  • 13
  • 151
  • 195
teubanks
  • 710
  • 1
  • 5
  • 10
  • 1
    It's deadlocking because you're enqueuing a block on the main queue -- which is on the main thread -- and telling the main thread to wait for that block to complete. – jscs Sep 11 '12 at 23:07
  • Since the answer that you've accepted doesn't at all address the function stubbing you asked about, I've removed that part from the question. The deadlocking question has already been answered multiple times (and is in the docs, too): http://stackoverflow.com/questions/7816159/dispatch-sync-on-main-queue-hangs-in-unit-test, http://stackoverflow.com/questions/10315645/app-blocks-while-dipatching-a-queue, http://stackoverflow.com/questions/10984732/gcd-why-cant-we-use-a-dispatch-sync-on-the-current-queue, but I thought the question about stubbing a GCD function was quite interesting. – jscs Sep 11 '12 at 23:37
  • Thanks. If I had known this was a deadlock issue I could've tailored the question appropriately or found a solution by searching google. It would still be nice to know how to stub dispatch_sync, but maybe that's not an appropriate solution. – teubanks Sep 11 '12 at 23:45
  • 1
    You could always post that question separately, even if you don't intend to use the answer. – jscs Sep 11 '12 at 23:49
  • I am doing exactly what is being answered here. Check with NSThread isMainThread and if it is not main thread use dispatch_sync but it still deadlocks..!! – user1010819 Oct 29 '13 at 08:54
  • @user1010819 This answer is probably relevant to your problem http://stackoverflow.com/questions/14716334/nsthread-ismainthread-always-returns-yes Basically, it says that queues are not the same thing as threads. Is there any reason why dispatch_async wouldn't work for what you're doing? You could probably avoid the code to check if you're on the main thread. – teubanks Nov 09 '13 at 06:49
  • I want them to happen synchronously.I mean dispatch_async on main thread will queue it on next cycle.My code outside the block might be dependent on code in block. – user1010819 Nov 10 '13 at 10:41

2 Answers2

57

For the second part of your question regarding the hint on the freeze:

When calling dispatch_sync on a queue, always verify that this queue is not already the current queue (dispatch_get_current_queue()). Because dispatch_sync will queue your block on the queue passed as the first parameter, and then will wait for this block to be executed before continuing.

So if the dispatch_get_current_queue() and the queue on which you enqueue your block are the same, namely the main queue in your case, the main queue will block on the call to dispatch_sync until… the main queue as executed the block, but it can't, as the queue is blocked, and you have a beautiful deadlock here.

One solution ([EDIT] up until iOS6):

dispatch_queue_t main = dispatch_get_main_queue();
dispatch_block_t block = ^
              {
                  [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationAuthenticationSuccess object:nil userInfo:ret];
              };
if (dispatch_get_current_queue() == main)
  block(); // execute the block directly, as main is already the current active queue
else
  dispatch_sync(main, block); // ask the main queue, which is not the current queue, to execute the block, and wait for it to be executed before continuing

[EDIT] Be careful, dispatch_get_current_queue() is only to be used for debugging purposes and never in production. In fact, dispatch_get_current_queue is deprecated since iOS6.1.3.

If you are in the specific case of the main queue (which is associated with the main thread only), you may instead test [NSThread isMainThread] as suggested by @meaning-matters.


By the way, are you sure you need to dispatch_sync in your case? I guess sending your notification a bit later, avoiding to block until it has been sent, is acceptable in your case, so you may also consider using dispatch_async (instead of using dispatch_sync and needing the queue comparison condition), which would avoid the deadlock issue too.

AliSoftware
  • 32,623
  • 6
  • 82
  • 77
  • This is a fantastic answer. The reason we're using dispatch_sync is because it's being called within a dispatch_async call (not sure if that makes sense). Put another way, it's one part of a synchronous process that happens within an asynchronous thread. – teubanks Sep 11 '12 at 23:27
  • 1
    Keep in mind that if you're using dispatch queues to serialise access to a shared resource, executing the block immediately can sometimes lead to subtle bugs as the dispatch_sync block will be executed before dispatch_async blocks targeting the same queue, even if the async block was submitted first. – Chris Devereux Sep 11 '12 at 23:37
  • 1
    This solution does not work on/from iOS 6.1.3 ([see my answer below](http://stackoverflow.com/a/15692778/1971013)). And `dispatch_get_current_queue()` is deprecated. – meaning-matters Jul 08 '13 at 20:31
42

dispatch_get_current_queue() is deprecated starting from iOS 6, and dispatch_get_current_queue() == dispatch_get_main_queue() was found to be false while on main thread on iOS 6.1.3.

In iOS 6 and beyond simply do:

dispatch_block_t block = ^
{
    <your code here>
};

if ([NSThread isMainThread])
{
    block();
}
else
{
    dispatch_sync(dispatch_get_main_queue(), block);
}
meaning-matters
  • 21,929
  • 10
  • 82
  • 142