Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to check if an NSThread is blocked?

I've always been interested in how to write the following code to use it for unit testing:

Is it possible to extend NSThread with a method that would check if a particular thread is blocked?

Right now I'am working with NSCondition: Xcode shows me the chain which is called by -wait to block the thread:

[NSCondition wait] 
pthread_cond_wait$UNIX2003
_pthread_cond_wait
__psynch_cvwait

Besides checking the locks done by NSCondition, if it is even possible, I would highly appreciate method working also for any other blocking capabilities (dispatch semaphores, condition locks, sleeping threads and so on, ) - I have no idea about Objective-C internals, if maybe they could be catched by one method or each needs its own.

Here is a simple example of what I would like to achieve. The mysterious method is called isBlocked.

// Some test case
// ...

__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];

dispatch_async(someQueue(), ^{
    thread = NSThread.currentThread; 

    [condition lock];
    [condition wait];
    [condition unlock];
});

while(1) {
    NSLog(@"Thread is blocked: %d", thread.isBlocked);
}

Note: I am not good at C and all this low-level POSIX stuff, so, please, be verbose.

Note 2: I am interested in solutions working for dispatch queues as well: if someone can show me how to test the fact that someQueue() is blocked by -[NSCondition wait] (not the fact that it is going to be blocked (fx hacking some code before -[condition wait] is run and the block is set), but the fact that thread/queue is blocked), I will accept this as an answer as much like I would do with working -[NSThread isBlocked] method.

Note 3: Suspecting bad news like "it is not possible", I claim that any ideas about catching the fact that -[condition wait] was run and the thread was set blocked (see Note 2) are appreciated and can be also accepted as an answer!

UPDATE 1 in address to the nice answer by Richard J. Ross III. Unfortunately, his answer does not work in my original example, the version which is closer to my real work (though it does not differ much from the example I've initially provided - sorry that I didn't include it in the first edition of the question):

// Example

// Here I've bootstrapped Richard's isLocking categories for both NSThread and NSCondition
// ...

// somewhere in SenTesting test case...
__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];
__block BOOL wePassedBlocking = NO;

dispatch_async(someQueue(), ^{
    thread = NSThread.currentThread; 

    [condition lock];
    [condition wait];
    [condition unlock];

    wePassedBlocking = YES; // (*) This line is occasionally never reached!
});

while(!thread.isWaitingOnCondition); // I want this loop to exit after the condition really locks someQueue() and _thread_ __.

// sleep(1);

[condition lock];
[condition broadcast]; // BUT SOMETIMES this line is called before -[condition wait] is called inside someQueue() so the entire test case becomes blocked!
[condition unlock];

while(!wePassedBlocking); // (*) And so this loop occasionally never ends!

If I uncomment sleep(1) test begins working very stable without any occasional locks!

This leads us to the problem, that Richard's category does set state exactly one line before the actual blocking is done meaning that sometimes test case's main thread catches this new state before we actually have someQueue/thread blocked because Richard's code does not contain any synchronization mechanisms: @synchronized, NSLock or something like that! I hope I am making a clear explanation of this tricky case. For anyone who has doubts about what I've posted here, I would say that I have been also experimenting with multiple queues and even more complex cases, and if needed I'm ready to provide more examples. Richard, thanks again for your effort, let's think more together, if you understand these my points!

UPDATE 2

I see the dead-end paradox: obviously, to really set the state of waitingOnCondition we need to wrap this state's change inside some synchronization closures, but the problem is that the closing one, unlocking the synchronization lock, should be called after -[condition wait], but it can't, because the thread is already blocked. Again, I hope I am describing it pretty clear.

like image 346
Stanislav Pankevich Avatar asked Mar 18 '13 01:03

Stanislav Pankevich


1 Answers

Here you go! It won't detect threads being waited on by anything other than -[NSCondition wait], but it could easily be extended to detect other kinds of waiting.

It's probably not the best implementation out there, but it does in fact work, and will do what you need it to.

#import <objc/runtime.h>

@implementation NSThread(isLocking)

static int waiting_condition_key;

-(BOOL) isWaitingOnCondition {
    // here, we sleep for a microsecond (1 millionth of a second) so that the
    // other thread can catch up,  and actually call 'wait'. This time
    // interval is so small that you will never notice it in an actual
    // application, it's just here because of how multithreaded
    // applications work.    
    usleep(1);

    BOOL val = [objc_getAssociatedObject(self, &waiting_condition_key) boolValue];

    // sleep before and after so it works on both edges
    usleep(1);

    return val;
}

-(void) setIsWaitingOnCondition:(BOOL) value {
        objc_setAssociatedObject(self, &waiting_condition_key, @(value), OBJC_ASSOCIATION_RETAIN);
}

@end

@implementation NSCondition(isLocking)

+(void) load {
    Method old = class_getInstanceMethod(self, @selector(wait));
    Method new = class_getInstanceMethod(self, @selector(_wait));

    method_exchangeImplementations(old, new);
}

-(void) _wait {
    // this is the replacement for the original wait method
    [[NSThread currentThread] setIsWaitingOnCondition:YES];

    // call  the original implementation, which now resides in the same name as this method
    [self _wait];

    [[NSThread currentThread] setIsWaitingOnCondition:NO];
}

@end

int main()
{
    __block NSCondition *condition = [NSCondition new];

    NSThread *otherThread = [[NSThread alloc] initWithTarget:^{
        NSLog(@"Thread started");

        [condition lock];
        [condition wait];
        [condition unlock];

        NSLog(@"Thread ended");
    } selector:@selector(invoke) object:nil];
    [otherThread start];

    while (![otherThread isWaitingOnCondition]);

    [condition lock];
    [condition signal];
    [condition unlock];

    NSLog(@"%i", [otherThread isWaitingOnCondition]);
}

Output:

2013-03-20 10:43:01.422 TestProj[11354:1803] Thread started
2013-03-20 10:43:01.424 TestProj[11354:1803] Thread ended
2013-03-20 10:43:01.425 TestProj[11354:303] 0
like image 91
Richard J. Ross III Avatar answered Oct 23 '22 04:10

Richard J. Ross III