1/*
2 * Copyright 2007-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "condition_variable.h"
7
8#include <new>
9#include <stdlib.h>
10#include <string.h>
11
12#include <KernelExport.h>
13
14// libroot
15#include <user_thread.h>
16
17// system
18#include <syscalls.h>
19#include <user_thread_defs.h>
20
21#include <lock.h>
22#include <util/AutoLock.h>
23
24#include "thread.h"
25
26
27#define STATUS_ADDED	1
28#define STATUS_WAITING	2
29
30
31static const int kConditionVariableHashSize = 512;
32
33
34struct ConditionVariableHashDefinition {
35	typedef const void* KeyType;
36	typedef	ConditionVariable ValueType;
37
38	size_t HashKey(const void* key) const
39		{ return (size_t)key; }
40	size_t Hash(ConditionVariable* variable) const
41		{ return (size_t)variable->fObject; }
42	bool Compare(const void* key, ConditionVariable* variable) const
43		{ return key == variable->fObject; }
44	ConditionVariable*& GetLink(ConditionVariable* variable) const
45		{ return variable->fNext; }
46};
47
48typedef BOpenHashTable<ConditionVariableHashDefinition> ConditionVariableHash;
49static ConditionVariableHash sConditionVariableHash;
50static mutex sConditionVariablesLock = MUTEX_INITIALIZER("condition variables");
51
52
53// #pragma mark - ConditionVariableEntry
54
55
56ConditionVariableEntry::ConditionVariableEntry()
57	: fVariable(NULL)
58{
59}
60
61
62ConditionVariableEntry::~ConditionVariableEntry()
63{
64	if (fVariable != NULL)
65		_RemoveFromVariable();
66}
67
68
69bool
70ConditionVariableEntry::Add(const void* object)
71{
72	ASSERT(object != NULL);
73
74	fThread = get_current_thread();
75
76	MutexLocker _(sConditionVariablesLock);
77
78	fVariable = sConditionVariableHash.Lookup(object);
79
80	if (fVariable == NULL) {
81		fWaitStatus = B_ENTRY_NOT_FOUND;
82		return false;
83	}
84
85	fWaitStatus = STATUS_ADDED;
86	fVariable->fEntries.Add(this);
87
88	return true;
89}
90
91
92status_t
93ConditionVariableEntry::Wait(uint32 flags, bigtime_t timeout)
94{
95	MutexLocker conditionLocker(sConditionVariablesLock);
96
97	if (fVariable == NULL)
98		return fWaitStatus;
99
100	user_thread* userThread = get_user_thread();
101
102	userThread->wait_status = 1;
103	fWaitStatus = STATUS_WAITING;
104
105	conditionLocker.Unlock();
106
107	status_t error;
108	while ((error = _kern_block_thread(flags, timeout)) == B_INTERRUPTED) {
109	}
110
111	_RemoveFromVariable();
112	return error;
113}
114
115
116status_t
117ConditionVariableEntry::Wait(const void* object, uint32 flags,
118	bigtime_t timeout)
119{
120	if (Add(object))
121		return Wait(flags, timeout);
122	return B_ENTRY_NOT_FOUND;
123}
124
125
126inline void
127ConditionVariableEntry::_AddToLockedVariable(ConditionVariable* variable)
128{
129	fThread = get_current_thread();
130	fVariable = variable;
131	fWaitStatus = STATUS_ADDED;
132	fVariable->fEntries.Add(this);
133}
134
135
136void
137ConditionVariableEntry::_RemoveFromVariable()
138{
139	MutexLocker _(sConditionVariablesLock);
140	if (fVariable != NULL) {
141		fVariable->fEntries.Remove(this);
142		fVariable = NULL;
143	}
144}
145
146
147// #pragma mark - ConditionVariable
148
149
150/*!	Initialization method for anonymous (unpublished) condition variables.
151*/
152void
153ConditionVariable::Init(const void* object, const char* objectType)
154{
155	fObject = object;
156	fObjectType = objectType;
157	new(&fEntries) EntryList;
158}
159
160
161void
162ConditionVariable::Publish(const void* object, const char* objectType)
163{
164	ASSERT(object != NULL);
165
166	fObject = object;
167	fObjectType = objectType;
168	new(&fEntries) EntryList;
169
170	MutexLocker locker(sConditionVariablesLock);
171
172	ASSERT(sConditionVariableHash.Lookup(object) == NULL);
173
174	sConditionVariableHash.InsertUnchecked(this);
175}
176
177
178void
179ConditionVariable::Unpublish()
180{
181	ASSERT(fObject != NULL);
182
183	MutexLocker locker(sConditionVariablesLock);
184
185	sConditionVariableHash.RemoveUnchecked(this);
186	fObject = NULL;
187	fObjectType = NULL;
188
189	if (!fEntries.IsEmpty())
190		_NotifyLocked(true, B_ENTRY_NOT_FOUND);
191}
192
193
194void
195ConditionVariable::Add(ConditionVariableEntry* entry)
196{
197	MutexLocker _(sConditionVariablesLock);
198	entry->_AddToLockedVariable(this);
199}
200
201
202status_t
203ConditionVariable::Wait(uint32 flags, bigtime_t timeout)
204{
205	ConditionVariableEntry entry;
206	Add(&entry);
207	return entry.Wait(flags, timeout);
208}
209
210
211int32
212ConditionVariable::_Notify(bool all, status_t result)
213{
214	MutexLocker locker(sConditionVariablesLock);
215
216	if (!fEntries.IsEmpty()) {
217		if (result > B_OK) {
218			panic("tried to notify with invalid result %" B_PRId32 "\n",
219				result);
220			result = B_ERROR;
221		}
222
223		return _NotifyLocked(all, result);
224	}
225	return 0;
226}
227
228
229/*! Called with interrupts disabled and the condition variable spinlock and
230	thread lock held.
231*/
232int32
233ConditionVariable::_NotifyLocked(bool all, status_t result)
234{
235	int32 notified = 0;
236
237	// dequeue and wake up the blocked threads
238	while (ConditionVariableEntry* entry = fEntries.RemoveHead()) {
239		entry->fVariable = NULL;
240
241		if (entry->fWaitStatus <= 0)
242			continue;
243
244		if (entry->fWaitStatus == STATUS_WAITING)
245			_kern_unblock_thread(get_thread_id(entry->fThread), result);
246
247		entry->fWaitStatus = result;
248
249		notified++;
250		if (!all)
251			break;
252	}
253
254	return notified;
255}
256
257
258// #pragma mark -
259
260
261void
262condition_variable_init()
263{
264	status_t error = sConditionVariableHash.Init(kConditionVariableHashSize);
265	if (error != B_OK) {
266		panic("condition_variable_init(): Failed to init hash table: %s",
267			strerror(error));
268	}
269}
270