How to use NSCondition when there are two different places that signal & wait

864 views Asked by At

Here's pseudo code of what I have in a method:

NSCondition condition = [[NSCondition alloc] init];
int         predicate = 0;

dispatch_sync(dispatch_get_main_queue(), ^
{
    [condition lock];   // Lock-0
});

bindBlock1ForDataReceived(^()
{
    // Not main thread here.
    // Get on main thread, because lock and unlock must be run on same thread.
    dispatch_sync(dispatch_get_main_queue(), ^
    {
        predicate = 1;
        [condition signal];
        [condition unlock]; <<<<---- "unlocked when not locked"
    });
});

bindBlock2ForNoDataAvailable(^()
{
    // Not main thread here.
    // Get on main thread, because lock and unlock must be run on same thread.
    dispatch_sync(dispatch_get_main_queue(), ^
    {
        predicate = 2;
        [condition signal];
        [condition unlock];
    });
});

[condition lock];  // Lock-1
while (predicate == 0)
{
    [condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
}
[condition unlock];

if (predicate == 2)
{
    [condition lock];  // Lock-2
    [condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
    [condition unlock];
}

The issue is that I get an "unlocked when not locked" warning from iOS (see above) when first event 2 occurs and then event 1.

Now let me explain what I'm trying to accomplish: This is part of a data fetcher. Normal cases data is received and block1 is executed: no issues. Sometimes the no-data block2 is spuriously executed first, shortly followed by block1; this is when I get the NSCondition warning. To catch this rare case, I wait for 2.0 seconds. Here's what happens:

  • Block2 signals the condition.
  • Lock-1 falls through.
  • predicate is no longer 0 so there's no wait.
  • The condition is unlocked again.
  • We then continue to the if-statement whose condition (predicate == 2) is true.
  • The method gets Lock-2 immediately. <<<< ROOT CAUSE
  • Subsequently the method wait for 2.0 seconds.
  • Within these 2 seconds block1 is executed and signals the condition.
  • Then block1 unlocks the condition & the method unlocks as well.

The root cause (see above) is that the lock is acquired by the method (the worker/consumer), while it should have been acquired by the data producer. I've spend a lot of time trying to figure this out; one of the thoughts I had is using two NSConditions, but I could not figure this out because things are rather intertwined.

Note: I find it strange that the warning does not appear at the unlock inside the if-statement.

Thanks for your time!

1

There are 1 answers

1
Ryan Heitner On BEST ANSWER

The simplest approach with more than lock is to use

dispatch_group_t confirmGroup = dispatch_group_create(); // 1

if (requestContacts) {
    dispatch_group_enter(confirmGroup); // 2
    [Extractor requestAccessAddressBook:^(BOOL isComplete) {
        if (isComplete) {
            dispatch_group_leave(confirmGroup); //2
        }
    }];


}
if (requestEvent) {
    dispatch_group_enter(confirmGroup); // 3       
    [Extractor requestAccessEvents:^(BOOL isComplete) {
        if (isComplete) {
            dispatch_group_leave(confirmGroup); // 3
        }
    }];
}
if (requestPhoto) {
    dispatch_group_enter(confirmGroup); // 4
    [Extractor requestAccessPhotos:^(BOOL isComplete) {
        if (isComplete) {
            dispatch_group_leave(confirmGroup); //4
        }
    }];
}
NSLog(@"dispatch_group_wait confirmations");
dispatch_group_wait(confirmGroup, DISPATCH_TIME_FOREVER); // 5