/*
 * 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;
}






