Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

semaphore: Not seeing my callback methods invoked, deadlock

I have two light-weight network requests that I'd like to perform concurrently, and then when both are done, invoke a block function.

I've created the method as follows:

- (void)loadWithCompletion:(void (^)())completion
{
    dispatch_semaphore_t customerSemaphore = dispatch_semaphore_create(0);
    dispatch_semaphore_t communitySemaphore = dispatch_semaphore_create(0);

    dispatch_async(dispatch_queue_create("mp.session.loader", DISPATCH_QUEUE_CONCURRENT), ^(void)
    {
        [_customerClient loadCustomerDetailsWithSuccess:^(MPCustomer* customer)
        {
            [self setCurrentCustomer:customer];
            dispatch_semaphore_signal(customerSemaphore);
        } error:^(NSError* error)
        {
            LogDebug(@"Got unexpected error loading customer details: %@", error);
        }];

        [_customerClient loadCommunityDetailsWithSuccess:^(MPCommunity* community)
        {
            [self setCurrentCommunity:community];
            dispatch_semaphore_signal(communitySemaphore);
        } error:^(NSError* error)
        {
            LogDebug(@"Got unexpected error loading customer details: %@", error);
        }];
    });

    dispatch_semaphore_wait(customerSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_wait(communitySemaphore, DISPATCH_TIME_FOREVER);

    if (completion)
    {
        completion();
    }
}

. . it ends up waiting forever. I see my two network requests kick-off, but I never the two callbacks from the two client invocations get invoked, and thus neither semaphore is signaled.

Why?

like image 220
Jasper Blues Avatar asked Nov 01 '22 13:11

Jasper Blues


2 Answers

I wonder if the dispatch queue that you create is being destroyed before it runs the block. But there is no point in creating a dispatch queue for concurrent operations. Instead do this:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)...

Update:

Also, have you checked that your network library is not calling back on the main queue? This will cause a deadlock.

like image 175
Andy Etheridge Avatar answered Nov 15 '22 04:11

Andy Etheridge


The first thing I noticed here was that if there is ever an error, you will hang forever, because you signal the semaphores from the "success" blocks but not the "failure" blocks. @AndyEtheridge is right about the perils of calling this from the main thread and then waiting for NSURLConnection callbacks that would also be delivered on the same run loop (which you're blocking). That said, this would arguably be better implemented with a more async pattern. Perhaps like this:

- (void)loadWithCompletion:(void (^)())completion
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_block_t workBlock = ^(void)
    {
        [_customerClient loadCustomerDetailsWithSuccess:^(MPCustomer* customer)
         {
             [self setCurrentCustomer:customer];
             dispatch_group_leave(group);
         } error:^(NSError* error)
         {
             LogDebug(@"Got unexpected error loading customer details: %@", error);
             dispatch_group_leave(group);
         }];

        [_customerClient loadCommunityDetailsWithSuccess:^(MPCommunity* community)
         {
             [self setCurrentCommunity:community];
             dispatch_group_leave(group);
         } error:^(NSError* error)
         {
             LogDebug(@"Got unexpected error loading customer details: %@", error);
             dispatch_group_leave(group);
         }];
    });

    dispatch_queue_t bgQueue = dispatch_queue_create("mp.session.loader", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_enter(group); // for the loadCustomer call
    dispatch_group_enter(group); // for the loadCommunity call

    dispatch_async(bgQueue, workBlock);

    dispatch_group_notify(group, [NSThread isMainThread] ? dispatch_get_main_queue() : bgQueue, completion);
}

This makes the waiting to call the final completion asynchronous, and specifically calls the completion on the main thread if the initial call to -loadWithCompletion: came from the main thread.

like image 20
ipmcc Avatar answered Nov 15 '22 05:11

ipmcc