13

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.

Community
  • 1
  • 1
Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
  • 1
    Threads are not "locked". A thread can be *holding* a lock, or perhaps *waiting on* a lock, though -- is one of those what you mean? –  Mar 18 '13 at 01:46
  • Sorry, if I don't use the right terms. But I am sure my example does describe what I want, doesn't it? **-[condition wait]** "stops" thread until the moment when -[condition signal] will be called from some other thread - I want to catch the fact that condition was set to wait and thread has stopped rolling. – Stanislav Pankevich Mar 18 '13 at 01:52
  • @Stanislaw Just write a category on NSThread that handles that, then. – CodaFi Mar 18 '13 at 01:57
  • @CodaFi, it is what I am asking here about ;) I don't know how to do it. – Stanislav Pankevich Mar 18 '13 at 01:58
  • @CodaFi you make it sound like it's a trivial answer.. if it is, please share with us yours – abbood Mar 20 '13 at 04:14
  • The answer is: no, it's not possible to check if a thread is blocked. – Ken Thomases Mar 20 '13 at 07:47
  • @KenThomases, bad news! I did suspect this bad end! But could you please format your answer as an Answer in authoritative manner? – Stanislav Pankevich Mar 20 '13 at 08:34
  • @KenThomases It may not be possible to check if it's blocked, but it would be trivial to add a bit of state to the thread – CodaFi Mar 20 '13 at 12:54
  • 2
    I dare say, most seasoned multithreading familiar developers don't use, don't need `isBlocked` kind of things. It might eventually backstab you in the end. – 9dan Mar 20 '13 at 14:50
  • @9dan, I did asked this question more likely for educational purposes: I was just interested, if it was even possible to write such method (especially because of UPDATE 2 and still not knowing how multithreading is really done behind the scenes). I myself do mean to use this solution only in very rare specific 1 or 2 cases. I hope, that the fact that such question is asked, will not make folks to move in the wrong direction and so I hope these comments will also appeal to the people's mind! – Stanislav Pankevich Mar 20 '13 at 16:42

2 Answers2

4

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
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
0

Here is a solution using dispatch_semaphore_t

PGFoo.h

#import <Foundation/Foundation.h>

@interface PGFoo : NSObject

- (void)longRunningAsynchronousMethod:(void (^)(NSInteger result))completion;

@end

PGFoo.m

#import "PGFoo.h"

@implementation PGFoo

- (void)longRunningAsynchronousMethod:(void (^)(NSInteger))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(5);
        completion(1);
    });
}

@end

Test Methods

- (void)testThatFailsBecauseItIsImpatient {
    PGFoo *foo = [[PGFoo alloc] init];
    __block NSInteger theResult = 0;
    [foo longRunningAsynchronousMethod:^(NSInteger result) {
        theResult = result;
    }];

    STAssertEquals(theResult, 1, nil);
}

- (void)testThatPassesBecauseItIsPatient {
    PGFoo *foo = [[PGFoo alloc] init];
    __block NSInteger theResult = 0;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [foo longRunningAsynchronousMethod:^(NSInteger result) {
        theResult = result;
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    STAssertEquals(theResult, 1, nil);
}

By using a dispatch_semaphore_t you can "track" whether a thread that is waiting on that semaphore is blocked. For every call of dispatch_semaphore_wait the semaphore's count is decremented and the thread waits until a call of dispatch_semaphore_signal is made, when dispatch_semaphore_signal is called the semaphore's count is incremented, if the count is incremented to a value greater than -1 the thread continues.

This solution fails to answer your question about checking whether an NSThread is "blocked" but I think it provides what you are reaching for, assuming you're not reaching to check on NSThread instances that are maintained within an existing framework.

Chris Wagner
  • 20,773
  • 8
  • 74
  • 95
  • The problem with dispatch semaphores is that they use queues, which are not equivalent to threads. – Richard J. Ross III Mar 20 '13 at 04:03
  • @RichardJ.RossIII so wait a minute.. do you mean to say that all Grand Central Dispatchs' methods use queues.. which are fundamentally different than threads in objective-c? – abbood Mar 20 '13 at 04:37
  • @ChrisWagner, thanks for the answer - I've got what it is about, but really it is _not_ about what I'm asking in my question. I want a code that will make my example work _exactly_ (or at least close enough) in the form I've written (method -[NSThread isBlocked]). If you could adapt your idea closer to what I'm asking - it would be great! – Stanislav Pankevich Mar 20 '13 at 04:50
  • @RichardJ.RossIII, I believe that it would be possible to adapt a code working for NSThread also for dispatch queues, if such code itself could be created. Though I see, it is a very tricky one, if not even impossible. – Stanislav Pankevich Mar 20 '13 at 04:50
  • 3
    @abbood that is correct. A single thread can actually have multiple queues running on it, and a single queue is *almost* equivalent to a while loop spawning blocks. Not entirely, though. – Richard J. Ross III Mar 20 '13 at 11:32
  • 1
    @RichardJ.RossIII Oh I see. Prolly that's why GCD is not a good choice for real-time.. I quote from this [discussion](http://stackoverflow.com/a/12454761/766570) "[GCD] is not acceptable for realtime audio. it uses pools and queues, and the realtime dependent work you submit to it can sit in the queue for a long time, and that is beyond your control. best to dedicate your work to a realtime high priority thread, and make the work on that thread very simple and very fast." – abbood Mar 20 '13 at 12:06
  • 1
    @abbood that is similar to what I've read too. I've never needed that level of control in my apps so I have been heavily reliant on GCD and NSOperation. The simplicity/abstraction of GCD over "real threads" is really nice when it works for your requirements. – Chris Wagner Mar 20 '13 at 16:46
  • @ChrisWagner and `NSOperation` is simply a wrapper around `GCD` right? so using `NSOperation` doesn't add any significant cost over `GCD` right? – abbood Mar 20 '13 at 17:15
  • 1
    @abbood Yes, Apple suggests always using the highest level API if possible, and NSOperation was rewritten to use GCD when GCD came out (iOS 4?). There are always limitations the higher you go, so it makes sense to use GCD directly at times. – Chris Wagner Mar 20 '13 at 17:20