[plain text]
/*
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
//
// objc_sync.m
//
// Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
//
#include <stdbool.h>
#include <stdlib.h>
#include <sys/time.h>
#include <pthread.h>
#include <errno.h>
#include <AssertMacros.h>
#include "objc-sync.h"
//
// Code by Nick Kledzik
//
// revised comments by Blaine
//
// Allocate a lock only when needed. Since few locks are needed at any point
// in time, keep them on a single list.
//
static pthread_mutexattr_t sRecursiveLockAttr;
static bool sRecursiveLockAttrIntialized = false;
static pthread_mutexattr_t* recursiveAttributes()
{
if ( !sRecursiveLockAttrIntialized ) {
int err = pthread_mutexattr_init(&sRecursiveLockAttr);
require_noerr_string(err, done, "pthread_mutexattr_init failed");
err = pthread_mutexattr_settype(&sRecursiveLockAttr, PTHREAD_MUTEX_RECURSIVE);
require_noerr_string(err, done, "pthread_mutexattr_settype failed");
sRecursiveLockAttrIntialized = true;
}
done:
return &sRecursiveLockAttr;
}
struct SyncData
{
struct SyncData* nextData; // only accessed while holding sTableLock
id object; // only accessed while holding sTableLock
unsigned int lockCount; // only accessed while holding sTableLock
pthread_mutex_t mutex;
pthread_cond_t conditionVariable;
};
typedef struct SyncData SyncData;
static pthread_mutex_t sTableLock = PTHREAD_MUTEX_INITIALIZER;
static SyncData* sDataList = NULL;
enum usage { ACQUIRE, RELEASE, CHECK };
static SyncData* id2data(id object, enum usage why)
{
SyncData* result = NULL;
int err;
pthread_mutex_lock(&sTableLock);
// Walk in-use list looking for matching object
// sTableLock keeps other threads from winning an allocation race
// for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
SyncData* firstUnused = NULL;
SyncData* p;
for (p = sDataList; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
goto done;
}
if ( (firstUnused == NULL) && (p->object == NULL) )
firstUnused = p;
}
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = object;
result->lockCount = 0; // sanity
goto done;
}
// malloc a new SyncData and add to list.
// XXX calling malloc with a global lock held is bad practice,
// might be worth releasing the lock, mallocing, and searching again.
// But since we never free these guys we won't be stuck in malloc very often.
result = (SyncData*)malloc(sizeof(SyncData));
result->object = object;
result->lockCount = 0;
err = pthread_mutex_init(&result->mutex, recursiveAttributes());
require_noerr_string(err, done, "pthread_mutex_init failed");
err = pthread_cond_init(&result->conditionVariable, NULL);
require_noerr_string(err, done, "pthread_cond_init failed");
result->nextData = sDataList;
sDataList = result;
done:
if ( result != NULL ) {
switch ( why ) {
case ACQUIRE:
result->lockCount++;
break;
case RELEASE:
result->lockCount--;
if ( result->lockCount == 0 )
result->object = NULL; // now recycled
break;
case CHECK:
// do nothing
break;
}
}
pthread_mutex_unlock(&sTableLock);
return result;
}
// Begin synchronizing on 'obj'.
// Allocates recursive pthread_mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed");
result = pthread_mutex_lock(&data->mutex);
require_noerr_string(result, done, "pthread_mutex_lock failed");
} else {
// @synchronized(nil) does nothing
}
done:
return result;
}
// End synchronizing on 'obj'.
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR, "id2data failed");
result = pthread_mutex_unlock(&data->mutex);
require_noerr_string(result, done, "pthread_mutex_unlock failed");
} else {
// @synchronized(nil) does nothing
}
done:
if ( result == EPERM )
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
return result;
}
// Temporarily release lock on 'obj' and wait for another thread to notify on 'obj'
// Return OBJC_SYNC_SUCCESS, OBJC_SYNC_NOT_OWNING_THREAD_ERROR, OBJC_SYNC_TIMED_OUT, OBJC_SYNC_INTERRUPTED
int objc_sync_wait(id obj, long long milliSecondsMaxWait)
{
int result = OBJC_SYNC_SUCCESS;
SyncData* data = id2data(obj, CHECK);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR, "id2data failed");
// XXX need to retry cond_wait under out-of-our-control failures
if ( milliSecondsMaxWait == 0 ) {
result = pthread_cond_wait(&data->conditionVariable, &data->mutex);
require_noerr_string(result, done, "pthread_cond_wait failed");
}
else {
struct timespec maxWait;
maxWait.tv_sec = milliSecondsMaxWait / 1000;
maxWait.tv_nsec = (milliSecondsMaxWait - (maxWait.tv_sec * 1000)) * 1000000;
result = pthread_cond_timedwait_relative_np(&data->conditionVariable, &data->mutex, &maxWait);
require_noerr_string(result, done, "pthread_cond_timedwait_relative_np failed");
}
// no-op to keep compiler from complaining about branch to next instruction
data = NULL;
done:
if ( result == EPERM )
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
else if ( result == ETIMEDOUT )
result = OBJC_SYNC_TIMED_OUT;
return result;
}
// Wake up another thread waiting on 'obj'
// Return OBJC_SYNC_SUCCESS, OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_notify(id obj)
{
int result = OBJC_SYNC_SUCCESS;
SyncData* data = id2data(obj, CHECK);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR, "id2data failed");
result = pthread_cond_signal(&data->conditionVariable);
require_noerr_string(result, done, "pthread_cond_signal failed");
done:
if ( result == EPERM )
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
return result;
}
// Wake up all threads waiting on 'obj'
// Return OBJC_SYNC_SUCCESS, OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_notifyAll(id obj)
{
int result = OBJC_SYNC_SUCCESS;
SyncData* data = id2data(obj, CHECK);
require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR, "id2data failed");
result = pthread_cond_broadcast(&data->conditionVariable);
require_noerr_string(result, done, "pthread_cond_broadcast failed");
done:
if ( result == EPERM )
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
return result;
}
Generated by GNU enscript 1.6.4.