AutoTestSynchronizer.m   [plain text]


/*
 * Copyright (c) 2009 Apple Inc. All rights reserved.
 *
 * @APPLE_APACHE_LICENSE_HEADER_START@
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * @APPLE_APACHE_LICENSE_HEADER_END@
 */
//
//  AutoTestSynchronizer.m
//  auto
//
//  Created by Josh Behnke on 6/2/08.
//  Copyright 2008 Apple Inc. All rights reserved.
//

#import "AutoTestSynchronizer.h"
#import <objc/objc-auto.h>


@implementation AutoTestSynchronizer

#define SYNCHRONIZER_COUNT 32
static NSArray *_synchronizers = nil;
static pthread_key_t _synchronizerKey;

// It is an error if this ever gets called. Threads should not exit with an associated synchronizer.
static void synchronizerDestructor(void *unused)
{
    printf("test bug: some thread didn't give up its synchronizer\n");
    exit(1);
}


@synthesize startingSelector=_startingSelector;
@synthesize stackPointers=_stackPointers;


+ (void)initialize
{
    // We just make a bunch of instances so we won't need to allocate during test execution.
    if (self == [AutoTestSynchronizer class]) {
        AutoTestSynchronizer *synchronizers[SYNCHRONIZER_COUNT];
        int i;
        for (i=0; i<SYNCHRONIZER_COUNT; i++) {
            synchronizers[i] = [[AutoTestSynchronizer alloc] init];
        }
        _synchronizers = [[NSArray alloc] initWithObjects:synchronizers count:SYNCHRONIZER_COUNT];
        if (pthread_key_create(&_synchronizerKey, synchronizerDestructor)) {
            printf("FAIL: could not create synchronizer key\n");
            exit(1);
        }
    }
}


+ (AutoTestSynchronizer *)checkoutSynchronizer
{
    // First see if there is an instance already associated with this thread
    AutoTestSynchronizer *synchronizer = [self mySynchronizer];
    
    // No? Then find an unassociated instance.
    if (synchronizer == nil) {
        @synchronized ([AutoTestSynchronizer class]) {
            for (AutoTestSynchronizer *s in _synchronizers) {
                if (!s->_thread) {
                    synchronizer = s;
                    break;
                }
            }
            
            // Associate the instance with the current thread.
            if (synchronizer) {
                synchronizer->_thread = pthread_self();
                pthread_setspecific(_synchronizerKey, synchronizer);
            }
        }
    }
    
    if (!synchronizer) {
        printf("Failed to check out synchronizer. Increase SYNCHRONIZER_COUNT?\n");
        exit(1);
    }
    return synchronizer;
}


+ (AutoTestSynchronizer *)mySynchronizer
{
    // Just check the thread key and see if there is an associated instance.
    AutoTestSynchronizer *synchronizer = (AutoTestSynchronizer *)pthread_getspecific(_synchronizerKey);
    return synchronizer;
}


// Disassociate the receiver from its thread (which must be the calling thread)
- (void)checkin
{
    @synchronized ([AutoTestSynchronizer class]) {
        _thread = nil;
        _selector = nil;
        [self setStackPointers:NULL];
        [self setStartingSelector:NULL];
        pthread_setspecific(_synchronizerKey, NULL);
    }
}


- (id)init
{
    self = [super init];
    if (self) {
        pthread_mutex_init(&_lock, NULL);
        pthread_cond_init(&_cond, NULL);
    }
    return self;
}


- (BOOL)isTesterThread
{
    // A tester thread will have set a stack pointer buffer
    return [self stackPointers] != NULL;
}


- (SEL)nextSelector
{
    return _selector;
}


- (void)setNextSelector:(SEL)nextSel
{
    pthread_mutex_lock(&_lock);
    if (_selector != NULL) {
        printf("setNextSelector: called, but next selector already set\n");
        exit(1);
    }
    if (_waiting) {
        printf("setNextSelector: called while a thread is waiting\n");
        exit(1);
    }
    _selector = nextSel;
    _waiting = NO;
    _signaled = NO;
    pthread_mutex_unlock(&_lock);
}


- (void)waitUntilSignaled
{
    // Note that _selector is cleared as a side effect.
    pthread_mutex_lock(&_lock);
    if (_selector) {
        _waiting = YES;
        objc_clear_stack(0);
        while (!_signaled)
            pthread_cond_wait(&_cond, &_lock);
        _waiting = NO;
        _selector = NULL;
    } else {
        printf("AutoTestSynchronizer waitUntilSignaled called but no selector set.\n");
        exit(1);
    }
    pthread_mutex_unlock(&_lock);
}


- (void)signal
{
    pthread_mutex_lock(&_lock);
    if (_signaled) {
        printf("signal called, but synchronizer already signaled!\n");
    }
    while (!_waiting && !_signaled)
        pthread_cond_wait(&_cond, &_lock);
    _signaled = YES;
    pthread_cond_signal(&_cond);
    pthread_mutex_unlock(&_lock);
}


@end