2007-08-10 00:03:17 +04:00
|
|
|
/*
|
|
|
|
* Copyright 2007, Ingo Weinhold, bonefish@cs.tu-berlin.de.
|
|
|
|
* Distributed under the terms of the MIT License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <condition_variable.h>
|
|
|
|
|
|
|
|
#include <new>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <debug.h>
|
|
|
|
#include <kscheduler.h>
|
2007-08-27 03:53:12 +04:00
|
|
|
#include <ksignal.h>
|
2007-08-10 00:03:17 +04:00
|
|
|
#include <int.h>
|
|
|
|
#include <thread.h>
|
|
|
|
#include <util/AutoLock.h>
|
|
|
|
|
|
|
|
|
|
|
|
static const int kConditionVariableHashSize = 64;
|
|
|
|
|
|
|
|
|
|
|
|
struct ConditionVariableHashDefinition {
|
|
|
|
typedef const void* KeyType;
|
|
|
|
typedef PrivateConditionVariable ValueType;
|
|
|
|
|
|
|
|
size_t HashKey(const void* key) const
|
|
|
|
{ return (size_t)key; }
|
|
|
|
size_t Hash(PrivateConditionVariable* variable) const
|
|
|
|
{ return (size_t)variable->fObject; }
|
|
|
|
bool Compare(const void* key, PrivateConditionVariable* variable) const
|
|
|
|
{ return key == variable->fObject; }
|
|
|
|
HashTableLink<PrivateConditionVariable>* GetLink(
|
|
|
|
PrivateConditionVariable* variable) const
|
|
|
|
{ return variable; }
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef OpenHashTable<ConditionVariableHashDefinition> ConditionVariableHash;
|
|
|
|
static ConditionVariableHash sConditionVariableHash;
|
|
|
|
static spinlock sConditionVariablesLock;
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
list_condition_variables(int argc, char** argv)
|
|
|
|
{
|
|
|
|
PrivateConditionVariable::ListAll();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
dump_condition_variable(int argc, char** argv)
|
|
|
|
{
|
|
|
|
if (argc < 2 || strlen(argv[1]) < 2
|
|
|
|
|| argv[1][0] != '0'
|
|
|
|
|| argv[1][1] != 'x') {
|
|
|
|
kprintf("%s: invalid argument, pass address\n", argv[0]);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
addr_t address = strtoul(argv[1], NULL, 0);
|
|
|
|
if (address == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
PrivateConditionVariable* variable = sConditionVariableHash.Lookup(
|
|
|
|
(void*)address);
|
|
|
|
|
|
|
|
if (variable == NULL) {
|
|
|
|
// It might be a direct pointer to a condition variable. Search the
|
|
|
|
// hash.
|
|
|
|
ConditionVariableHash::Iterator it(&sConditionVariableHash);
|
|
|
|
while (PrivateConditionVariable* hashVariable = it.Next()) {
|
|
|
|
if (hashVariable == (void*)address) {
|
|
|
|
variable = hashVariable;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (variable != NULL)
|
|
|
|
variable->Dump();
|
|
|
|
else
|
|
|
|
kprintf("no condition variable at or with key %p\n", (void*)address);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// #pragma mark - PrivateConditionVariableEntry
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
2007-08-27 03:53:12 +04:00
|
|
|
PrivateConditionVariableEntry::Add(const void* object,
|
|
|
|
PrivateConditionVariableEntry* threadNext)
|
2007-08-10 00:03:17 +04:00
|
|
|
{
|
|
|
|
ASSERT(object != NULL);
|
|
|
|
|
|
|
|
fThread = thread_get_current_thread();
|
2007-08-27 03:53:12 +04:00
|
|
|
fFlags = 0;
|
2007-08-28 00:17:31 +04:00
|
|
|
fResult = B_OK;
|
2007-08-10 00:03:17 +04:00
|
|
|
|
|
|
|
InterruptsLocker _;
|
|
|
|
SpinLocker locker(sConditionVariablesLock);
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// add to the list of entries for this thread
|
|
|
|
fThreadNext = threadNext;
|
|
|
|
if (threadNext) {
|
|
|
|
fThreadPrevious = threadNext->fThreadPrevious;
|
|
|
|
threadNext->fThreadPrevious = this;
|
|
|
|
} else
|
|
|
|
fThreadPrevious = NULL;
|
|
|
|
|
|
|
|
// add to the queue for the variable
|
2007-08-10 00:03:17 +04:00
|
|
|
fVariable = sConditionVariableHash.Lookup(object);
|
|
|
|
if (fVariable) {
|
2007-08-27 03:53:12 +04:00
|
|
|
fVariableNext = fVariable->fEntries;
|
2007-08-10 00:03:17 +04:00
|
|
|
fVariable->fEntries = this;
|
2007-08-28 00:17:31 +04:00
|
|
|
} else
|
|
|
|
fResult = B_ENTRY_NOT_FOUND;
|
2007-08-10 00:03:17 +04:00
|
|
|
|
|
|
|
return (fVariable != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-28 00:17:31 +04:00
|
|
|
status_t
|
2007-08-27 03:53:12 +04:00
|
|
|
PrivateConditionVariableEntry::Wait(uint32 flags)
|
2007-08-10 00:03:17 +04:00
|
|
|
{
|
|
|
|
if (!are_interrupts_enabled()) {
|
|
|
|
panic("wait_for_condition_variable_entry() called with interrupts "
|
|
|
|
"disabled");
|
2007-08-28 00:17:31 +04:00
|
|
|
return B_ERROR;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
InterruptsLocker _;
|
|
|
|
SpinLocker locker(sConditionVariablesLock);
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// get first entry for this thread
|
|
|
|
PrivateConditionVariableEntry* firstEntry = this;
|
|
|
|
while (firstEntry->fThreadPrevious)
|
|
|
|
firstEntry = firstEntry->fThreadPrevious;
|
|
|
|
|
|
|
|
// check whether any entry has already been notified
|
|
|
|
// (set the flags while at it)
|
|
|
|
PrivateConditionVariableEntry* entry = firstEntry;
|
|
|
|
while (entry) {
|
|
|
|
if (entry->fVariable == NULL)
|
2007-08-28 00:17:31 +04:00
|
|
|
return entry->fResult;
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
entry->fFlags = flags;
|
|
|
|
|
|
|
|
entry = entry->fThreadNext;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
2007-08-27 03:53:12 +04:00
|
|
|
|
|
|
|
// all entries are unnotified -- wait
|
|
|
|
struct thread* thread = thread_get_current_thread();
|
|
|
|
thread->next_state = B_THREAD_WAITING;
|
|
|
|
thread->condition_variable_entry = firstEntry;
|
|
|
|
thread->sem.blocking = -1;
|
|
|
|
|
|
|
|
GRAB_THREAD_LOCK();
|
|
|
|
locker.Unlock();
|
|
|
|
scheduler_reschedule();
|
|
|
|
RELEASE_THREAD_LOCK();
|
2007-08-28 00:17:31 +04:00
|
|
|
|
|
|
|
return firstEntry->fResult;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-28 00:17:31 +04:00
|
|
|
status_t
|
2007-08-27 03:53:12 +04:00
|
|
|
PrivateConditionVariableEntry::Wait(const void* object, uint32 flags)
|
2007-08-10 00:03:17 +04:00
|
|
|
{
|
2007-08-27 03:53:12 +04:00
|
|
|
if (Add(object, NULL))
|
2007-08-28 00:17:31 +04:00
|
|
|
return Wait(flags);
|
|
|
|
return B_ENTRY_NOT_FOUND;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
/*! Removes the entry from its variable.
|
|
|
|
Interrupts must be disabled, sConditionVariablesLock must be held.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
PrivateConditionVariableEntry::_Remove()
|
|
|
|
{
|
|
|
|
if (!fVariable)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// fast path, if we're first in queue
|
|
|
|
if (this == fVariable->fEntries) {
|
|
|
|
fVariable->fEntries = fVariableNext;
|
|
|
|
fVariableNext = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we're not the first entry -- find our previous entry
|
|
|
|
PrivateConditionVariableEntry* entry = fVariable->fEntries;
|
|
|
|
while (entry->fVariableNext) {
|
|
|
|
if (this == entry->fVariableNext) {
|
|
|
|
entry->fVariableNext = fVariableNext;
|
|
|
|
fVariableNext = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = entry->fVariableNext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class PrivateConditionVariableEntry::Private {
|
|
|
|
public:
|
|
|
|
inline Private(PrivateConditionVariableEntry& entry)
|
|
|
|
: fEntry(entry)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2007-08-28 00:17:31 +04:00
|
|
|
inline uint32 Flags() const { return fEntry.fFlags; }
|
|
|
|
inline void Remove() const { fEntry._Remove(); }
|
|
|
|
inline void SetResult(status_t result) { fEntry.fResult = result; }
|
2007-08-27 03:53:12 +04:00
|
|
|
|
|
|
|
private:
|
|
|
|
PrivateConditionVariableEntry& fEntry;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2007-08-10 00:03:17 +04:00
|
|
|
// #pragma mark - PrivateConditionVariable
|
|
|
|
|
|
|
|
|
|
|
|
/*static*/ void
|
|
|
|
PrivateConditionVariable::ListAll()
|
|
|
|
{
|
|
|
|
kprintf(" variable object (type) waiting threads\n");
|
|
|
|
kprintf("------------------------------------------------------------\n");
|
|
|
|
ConditionVariableHash::Iterator it(&sConditionVariableHash);
|
|
|
|
while (PrivateConditionVariable* variable = it.Next()) {
|
|
|
|
// count waiting threads
|
|
|
|
int count = 0;
|
|
|
|
PrivateConditionVariableEntry* entry = variable->fEntries;
|
|
|
|
while (entry) {
|
|
|
|
count++;
|
2007-08-27 03:53:12 +04:00
|
|
|
entry = entry->fVariableNext;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
kprintf("%p %p %-20s %15d\n", variable, variable->fObject,
|
|
|
|
variable->fObjectType, count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
PrivateConditionVariable::Dump()
|
|
|
|
{
|
|
|
|
kprintf("condition variable %p\n", this);
|
|
|
|
kprintf(" object: %p (%s)\n", fObject, fObjectType);
|
|
|
|
kprintf(" threads:");
|
|
|
|
PrivateConditionVariableEntry* entry = fEntries;
|
|
|
|
while (entry) {
|
|
|
|
kprintf(" %ld", entry->fThread->id);
|
2007-08-27 03:53:12 +04:00
|
|
|
entry = entry->fVariableNext;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
kprintf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
PrivateConditionVariable::Publish(const void* object, const char* objectType)
|
|
|
|
{
|
|
|
|
ASSERT(object != NULL);
|
|
|
|
|
|
|
|
fObject = object;
|
|
|
|
fObjectType = objectType;
|
|
|
|
fEntries = NULL;
|
|
|
|
|
|
|
|
InterruptsLocker _;
|
|
|
|
SpinLocker locker(sConditionVariablesLock);
|
|
|
|
|
|
|
|
ASSERT_PRINT(sConditionVariableHash.Lookup(object) == NULL,
|
|
|
|
"condition variable: %p\n", sConditionVariableHash.Lookup(object));
|
|
|
|
|
|
|
|
sConditionVariableHash.InsertUnchecked(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
PrivateConditionVariable::Unpublish()
|
|
|
|
{
|
|
|
|
ASSERT(fObject != NULL);
|
|
|
|
|
|
|
|
InterruptsLocker _;
|
|
|
|
SpinLocker locker(sConditionVariablesLock);
|
|
|
|
|
|
|
|
#if KDEBUG
|
|
|
|
PrivateConditionVariable* variable = sConditionVariableHash.Lookup(fObject);
|
|
|
|
if (variable != this) {
|
|
|
|
panic("Condition variable %p not published, found: %p", this, variable);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
sConditionVariableHash.RemoveUnchecked(this);
|
|
|
|
fObject = NULL;
|
|
|
|
fObjectType = NULL;
|
|
|
|
|
|
|
|
if (fEntries)
|
2007-08-28 00:17:31 +04:00
|
|
|
_Notify(true, B_ENTRY_NOT_FOUND);
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2007-08-27 03:53:12 +04:00
|
|
|
PrivateConditionVariable::Notify(bool all)
|
2007-08-10 00:03:17 +04:00
|
|
|
{
|
|
|
|
ASSERT(fObject != NULL);
|
|
|
|
|
|
|
|
InterruptsLocker _;
|
|
|
|
SpinLocker locker(sConditionVariablesLock);
|
|
|
|
|
|
|
|
#if KDEBUG
|
|
|
|
PrivateConditionVariable* variable = sConditionVariableHash.Lookup(fObject);
|
|
|
|
if (variable != this) {
|
|
|
|
panic("Condition variable %p not published, found: %p", this, variable);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (fEntries)
|
2007-08-28 00:17:31 +04:00
|
|
|
_Notify(all, B_OK);
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//! Called with interrupts disabled and the condition variable spinlock held.
|
|
|
|
void
|
2007-08-28 00:17:31 +04:00
|
|
|
PrivateConditionVariable::_Notify(bool all, status_t result)
|
2007-08-10 00:03:17 +04:00
|
|
|
{
|
|
|
|
// dequeue and wake up the blocked threads
|
|
|
|
GRAB_THREAD_LOCK();
|
|
|
|
|
|
|
|
while (PrivateConditionVariableEntry* entry = fEntries) {
|
2007-08-27 03:53:12 +04:00
|
|
|
fEntries = entry->fVariableNext;
|
|
|
|
struct thread* thread = entry->fThread;
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-28 00:17:31 +04:00
|
|
|
if (thread->condition_variable_entry != NULL)
|
|
|
|
thread->condition_variable_entry->fResult = result;
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
entry->fVariableNext = NULL;
|
2007-08-10 00:03:17 +04:00
|
|
|
entry->fVariable = NULL;
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// remove other entries of this thread from their respective variables
|
|
|
|
PrivateConditionVariableEntry* otherEntry = entry->fThreadPrevious;
|
|
|
|
while (otherEntry) {
|
|
|
|
otherEntry->_Remove();
|
|
|
|
otherEntry = otherEntry->fThreadPrevious;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
otherEntry = entry->fThreadNext;
|
|
|
|
while (otherEntry) {
|
|
|
|
otherEntry->_Remove();
|
|
|
|
otherEntry = otherEntry->fThreadNext;
|
|
|
|
}
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// wake up the thread
|
|
|
|
thread->condition_variable_entry = NULL;
|
|
|
|
if (thread->state == B_THREAD_WAITING) {
|
|
|
|
thread->state = B_THREAD_READY;
|
|
|
|
scheduler_enqueue_in_run_queue(thread);
|
|
|
|
}
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
if (!all)
|
|
|
|
break;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
RELEASE_THREAD_LOCK();
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// #pragma mark -
|
2007-08-10 00:03:17 +04:00
|
|
|
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
/*! Interrupts must be disabled, thread lock must be held. Note, that the thread
|
|
|
|
lock may temporarily be released.
|
|
|
|
*/
|
|
|
|
status_t
|
|
|
|
condition_variable_interrupt_thread(struct thread* thread)
|
2007-08-10 00:03:17 +04:00
|
|
|
{
|
2007-08-27 03:53:12 +04:00
|
|
|
if (thread == NULL || thread->state != B_THREAD_WAITING
|
|
|
|
|| thread->condition_variable_entry == NULL) {
|
|
|
|
return B_BAD_VALUE;
|
|
|
|
}
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
thread_id threadID = thread->id;
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// We also need the condition variable spin lock, so, in order to respect
|
|
|
|
// the locking order, we must temporarily release the thread lock.
|
|
|
|
RELEASE_THREAD_LOCK();
|
2007-08-10 00:03:17 +04:00
|
|
|
SpinLocker locker(sConditionVariablesLock);
|
2007-08-27 03:53:12 +04:00
|
|
|
GRAB_THREAD_LOCK();
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// re-get the thread and do the checks again
|
|
|
|
thread = thread_get_thread_struct_locked(threadID);
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
if (thread != NULL || thread->state != B_THREAD_WAITING
|
|
|
|
|| thread->condition_variable_entry == NULL) {
|
|
|
|
return B_BAD_VALUE;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
PrivateConditionVariableEntry* entry = thread->condition_variable_entry;
|
|
|
|
uint32 flags = PrivateConditionVariableEntry::Private(*entry).Flags();
|
2007-08-10 00:03:17 +04:00
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// interruptable?
|
|
|
|
if ((flags & B_CAN_INTERRUPT) == 0
|
|
|
|
&& ((flags & B_KILL_CAN_INTERRUPT) == 0
|
|
|
|
|| (thread->sig_pending & KILL_SIGNALS) == 0)) {
|
|
|
|
return B_NOT_ALLOWED;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
2007-08-28 00:17:31 +04:00
|
|
|
PrivateConditionVariableEntry::Private(*entry).SetResult(B_INTERRUPTED);
|
|
|
|
|
2007-08-27 03:53:12 +04:00
|
|
|
// remove all of the thread's entries from their variables
|
|
|
|
while (entry) {
|
|
|
|
PrivateConditionVariableEntry::Private(*entry).Remove();
|
|
|
|
entry = entry->ThreadNext();
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
2007-08-27 03:53:12 +04:00
|
|
|
|
|
|
|
// wake up the thread
|
|
|
|
thread->condition_variable_entry = NULL;
|
|
|
|
thread->state = B_THREAD_READY;
|
|
|
|
scheduler_enqueue_in_run_queue(thread);
|
|
|
|
|
|
|
|
return B_OK;
|
2007-08-10 00:03:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
condition_variable_init()
|
|
|
|
{
|
|
|
|
new(&sConditionVariableHash) ConditionVariableHash(
|
|
|
|
kConditionVariableHashSize);
|
|
|
|
|
|
|
|
status_t error = sConditionVariableHash.InitCheck();
|
|
|
|
if (error != B_OK) {
|
|
|
|
panic("condition_variable_init(): Failed to init hash table: %s",
|
|
|
|
strerror(error));
|
|
|
|
}
|
|
|
|
|
|
|
|
add_debugger_command("condition_variable", &dump_condition_variable,
|
|
|
|
"Dump condition_variable");
|
|
|
|
add_debugger_command("condition_variables", &list_condition_variables,
|
|
|
|
"List condition variables");
|
|
|
|
}
|
|
|
|
|