Process deadlock with nested dispatch_sync/dispath_async

553 Views Asked by At

I'm playing around with UIDocument recently and confuse with concept on dispatch_sync/dispath_async

I have a method that fetch all documents information

for (int i=0; i < noteDocuments.count; i++) {            
    NSURL * fileURL = [noteDocuments objectAtIndex:i];

    NoteDocument *doc = [[NoteDocument alloc] initWithFileURL:fileURL];
    [doc openWithCompletionHandler:^(BOOL success) {
        [doc closeWithCompletionHandler:^(BOOL success) {
            [self addOrUpdateInfoWithDoc:doc];
        }];
    }];
}

Everything work fine with this simple approach.

Then I try to dispatch_sync/dispath_async

First with dispath_sync

dispatch_sync(dispatch_queue_create("test", DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
for (int i=0; i < noteDocuments.count; i++) {            
    NSURL * fileURL = [noteDocuments objectAtIndex:i];

    NoteDocument *doc = [[NoteDocument alloc] initWithFileURL:fileURL];
    [doc openWithCompletionHandler:^(BOOL success) {
        [doc closeWithCompletionHandler:^(BOOL success) {
            [self addOrUpdateInfoWithDoc:doc];
        }];
    }];
}
});

After add dispatch_sync like this, method never finish. This confusing me because dispath_sync should return to main queue after block was executed which in this case an openWithCompletionHandler should return immediately.

Then with curiosity I change it to dispatch_async (just for experiment)

dispatch_async(dispatch_queue_create("test", DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
for (int i=0; i < noteDocuments.count; i++) {            
    NSURL * fileURL = [noteDocuments objectAtIndex:i];

    NoteDocument *doc = [[NoteDocument alloc] initWithFileURL:fileURL];
    [doc openWithCompletionHandler:^(BOOL success) {
        [doc closeWithCompletionHandler:^(BOOL success) {
            [self addOrUpdateInfoWithDoc:doc];
        }];
    }];
}
});

The result also confusing me, I got EXC_BAD_INSTRUCTION on line [doc openWithCompletionHandler:^(BOOL success) {

Can anyone explain what going under the hood ? This is what I understand, but it obviously wrong.

This is what I understand, but it obviously wrong

1

There are 1 best solutions below

2
On BEST ANSWER

I believe NoteDocument class is derived from UIDocument class.

So -openWithCompletionHandler: completionHandler is invoked on the main queue.

-openWithCompletionHandler: Parameters: completionHandler

A block with code to execute after the open operation concludes.

The block is invoked on the main queue.

Also I believe the following your code on the main queue (the main thread).

dispatch_sync(dispatch_queue_create("test", DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
...

It means you intended to wait to finish the queue in the main queue. Yes, it blocks the main queue until the queue was finished.

openWithCompletionHandler: finished to open the document, and then it submitted the completionHandler block to the main queue, but the main queue had been blocked by dispatch_sync. So the completionHandler block will never be invoked. Thus, the "test" queue will never finish and dispatch_sync also will never finish.

And EXC_BAD_INSTRUCTION, I doubt -openWithCompletionHandler: could be called from the other thread. I'm not sure but you might need to call the method from the main thread.

By the way, you might misunderstand the 2nd argument of dispatch_queue_create.

dispatch_queue_create("test", DISPATCH_QUEUE_PRIORITY_DEFAULT)

The 2nd argument is attr, it's not for the priority of the queue.

BSD Library Functions Manual dispatch_queue_create(3)

CREATION

The attr argument specifies the type of queue to create and must be either DISPATCH_QUEUE_SERIAL or DISPATCH_QUEUE_CONCURRENT.

Luckily, it's not problem in this case. In '''/usr/include/dispatch/queue.h'''

#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0

is the same as

#define DISPATCH_QUEUE_SERIAL NULL

EDIT

If UIDocument -openWithCompletionHandler: is ordinary method, it wouldn't cause DEADLOCK. It would be the following sequence.

 main queue                 test queue
  runloop
     |
     |
dispatch_sync [your block] ---->
(*BLOCKED*)                [your block]
                                |
                    UIDocument -openWithCompletionHandler:
                                |
                        (opening document...)
                                |
                         (finished to open)
                                |
      <------------------ dispatch_async [the completion block]
                                |
                               DONE
(Wake up from dispatch_sync
because test queue was finished)
     |
     |
the end of the runloop
 execute blocks
     |
[the completion block]
     |
  blocks done
     |
     |
go to the top of the runloop

But, according to LLDB stack trace, it seems a bit different from the other method.

#0  0x0358dfb6 in semaphore_wait_trap ()
#1  0x032504bf in _dispatch_thread_semaphore_wait ()
#2  0x03249e4b in _dispatch_barrier_sync_f_slow ()
#3  0x0324ee3a in _dispatch_barrier_sync_f ()
#4  0x0324a728 in _dispatch_barrier_sync_slow ()
#5  0x0324a61c in dispatch_barrier_sync ()
#6  0x00a9044b in -[UIDocument(UIDocumentInternal) _performBlock:synchronouslyOnQueue:] ()
#7  0x00a9048d in -[UIDocument(UIDocumentInternal) _performBlockSynchronouslyOnMainThread:] ()
#8  0x00a866a3 in -[UIDocument _registerAsFilePresenterIfNecessary] ()
#9  0x00a87891 in -[UIDocument openWithCompletionHandler:] ()

So the sequence would be the following.

 main queue                 test queue
  runloop
     |
     |
dispatch_sync [your block] ---->
(*BLOCKED*)                [your block]
                                |
                    UIDocument -openWithCompletionHandler:
                                |
                    UIDocument _performBlockSynchronouslyOnMainThread:
                                |
      <------------- dispatch_sync [UIDocument internal block]
                           (*BLOCKED*)

Thus, DEADLOCK.