root/src/tests/add-ons/kernel/kernelland_emu/condition_variable.cpp
/*
 * Copyright 2007-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include "condition_variable.h"

#include <new>
#include <stdlib.h>
#include <string.h>

#include <KernelExport.h>

// libroot
#include <user_thread.h>

// system
#include <syscalls.h>
#include <user_thread_defs.h>

#include <lock.h>
#include <util/AutoLock.h>

#include "thread.h"


#define STATUS_ADDED    1
#define STATUS_WAITING  2


static const int kConditionVariableHashSize = 512;


struct ConditionVariableHashDefinition {
        typedef const void* KeyType;
        typedef ConditionVariable ValueType;

        size_t HashKey(const void* key) const
                { return (size_t)key; }
        size_t Hash(ConditionVariable* variable) const
                { return (size_t)variable->fObject; }
        bool Compare(const void* key, ConditionVariable* variable) const
                { return key == variable->fObject; }
        ConditionVariable*& GetLink(ConditionVariable* variable) const
                { return variable->fNext; }
};

typedef BOpenHashTable<ConditionVariableHashDefinition> ConditionVariableHash;
static ConditionVariableHash sConditionVariableHash;
static mutex sConditionVariablesLock = MUTEX_INITIALIZER("condition variables");


// #pragma mark - ConditionVariableEntry


ConditionVariableEntry::ConditionVariableEntry()
        : fVariable(NULL)
{
}


ConditionVariableEntry::~ConditionVariableEntry()
{
        if (fVariable != NULL)
                _RemoveFromVariable();
}


bool
ConditionVariableEntry::Add(const void* object)
{
        ASSERT(object != NULL);

        fThread = get_current_thread();

        MutexLocker _(sConditionVariablesLock);

        fVariable = sConditionVariableHash.Lookup(object);

        if (fVariable == NULL) {
                fWaitStatus = B_ENTRY_NOT_FOUND;
                return false;
        }

        fWaitStatus = STATUS_ADDED;
        fVariable->fEntries.Add(this);

        return true;
}


status_t
ConditionVariableEntry::Wait(uint32 flags, bigtime_t timeout)
{
        MutexLocker conditionLocker(sConditionVariablesLock);

        if (fVariable == NULL)
                return fWaitStatus;

        user_thread* userThread = get_user_thread();

        userThread->wait_status = 1;
        fWaitStatus = STATUS_WAITING;

        conditionLocker.Unlock();

        status_t error;
        while ((error = _kern_block_thread(flags, timeout)) == B_INTERRUPTED) {
        }

        _RemoveFromVariable();
        return error;
}


status_t
ConditionVariableEntry::Wait(const void* object, uint32 flags,
        bigtime_t timeout)
{
        if (Add(object))
                return Wait(flags, timeout);
        return B_ENTRY_NOT_FOUND;
}


inline void
ConditionVariableEntry::_AddToLockedVariable(ConditionVariable* variable)
{
        fThread = get_current_thread();
        fVariable = variable;
        fWaitStatus = STATUS_ADDED;
        fVariable->fEntries.Add(this);
}


void
ConditionVariableEntry::_RemoveFromVariable()
{
        MutexLocker _(sConditionVariablesLock);
        if (fVariable != NULL) {
                fVariable->fEntries.Remove(this);
                fVariable = NULL;
        }
}


// #pragma mark - ConditionVariable


/*!     Initialization method for anonymous (unpublished) condition variables.
*/
void
ConditionVariable::Init(const void* object, const char* objectType)
{
        fObject = object;
        fObjectType = objectType;
        new(&fEntries) EntryList;
}


void
ConditionVariable::Publish(const void* object, const char* objectType)
{
        ASSERT(object != NULL);

        fObject = object;
        fObjectType = objectType;
        new(&fEntries) EntryList;

        MutexLocker locker(sConditionVariablesLock);

        ASSERT(sConditionVariableHash.Lookup(object) == NULL);

        sConditionVariableHash.InsertUnchecked(this);
}


void
ConditionVariable::Unpublish()
{
        ASSERT(fObject != NULL);

        MutexLocker locker(sConditionVariablesLock);

        sConditionVariableHash.RemoveUnchecked(this);
        fObject = NULL;
        fObjectType = NULL;

        if (!fEntries.IsEmpty())
                _NotifyLocked(true, B_ENTRY_NOT_FOUND);
}


void
ConditionVariable::Add(ConditionVariableEntry* entry)
{
        MutexLocker _(sConditionVariablesLock);
        entry->_AddToLockedVariable(this);
}


status_t
ConditionVariable::Wait(uint32 flags, bigtime_t timeout)
{
        ConditionVariableEntry entry;
        Add(&entry);
        return entry.Wait(flags, timeout);
}


int32
ConditionVariable::_Notify(bool all, status_t result)
{
        MutexLocker locker(sConditionVariablesLock);

        if (!fEntries.IsEmpty()) {
                if (result > B_OK) {
                        panic("tried to notify with invalid result %" B_PRId32 "\n",
                                result);
                        result = B_ERROR;
                }

                return _NotifyLocked(all, result);
        }
        return 0;
}


/*! Called with interrupts disabled and the condition variable spinlock and
        thread lock held.
*/
int32
ConditionVariable::_NotifyLocked(bool all, status_t result)
{
        int32 notified = 0;

        // dequeue and wake up the blocked threads
        while (ConditionVariableEntry* entry = fEntries.RemoveHead()) {
                entry->fVariable = NULL;

                if (entry->fWaitStatus <= 0)
                        continue;

                if (entry->fWaitStatus == STATUS_WAITING)
                        _kern_unblock_thread(get_thread_id(entry->fThread), result);

                entry->fWaitStatus = result;

                notified++;
                if (!all)
                        break;
        }

        return notified;
}


// #pragma mark -


void
condition_variable_init()
{
        status_t error = sConditionVariableHash.Init(kConditionVariableHashSize);
        if (error != B_OK) {
                panic("condition_variable_init(): Failed to init hash table: %s",
                        strerror(error));
        }
}