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
56bool
57ConditionVariableEntry::Add(const void* object)
58{
59	ASSERT(object != NULL);
60
61	fThread = get_current_thread();
62
63	MutexLocker _(sConditionVariablesLock);
64
65	fVariable = sConditionVariableHash.Lookup(object);
66
67	if (fVariable == NULL) {
68		fWaitStatus = B_ENTRY_NOT_FOUND;
69		return false;
70	}
71
72	fWaitStatus = STATUS_ADDED;
73	fVariable->fEntries.Add(this);
74
75	return true;
76}
77
78
79status_t
80ConditionVariableEntry::Wait(uint32 flags, bigtime_t timeout)
81{
82	MutexLocker conditionLocker(sConditionVariablesLock);
83
84	if (fVariable == NULL)
85		return fWaitStatus;
86
87	user_thread* userThread = get_user_thread();
88
89	userThread->wait_status = 1;
90	fWaitStatus = STATUS_WAITING;
91
92	conditionLocker.Unlock();
93
94	status_t error;
95	while ((error = _kern_block_thread(flags, timeout)) == B_INTERRUPTED) {
96	}
97
98	conditionLocker.Lock();
99
100	// remove entry from variable, if not done yet
101	if (fVariable != NULL) {
102		fVariable->fEntries.Remove(this);
103		fVariable = NULL;
104	}
105
106	return error;
107}
108
109
110status_t
111ConditionVariableEntry::Wait(const void* object, uint32 flags,
112	bigtime_t timeout)
113{
114	if (Add(object))
115		return Wait(flags, timeout);
116	return B_ENTRY_NOT_FOUND;
117}
118
119
120inline void
121ConditionVariableEntry::AddToLockedVariable(ConditionVariable* variable)
122{
123	fThread = get_current_thread();
124	fVariable = variable;
125	fWaitStatus = STATUS_ADDED;
126	fVariable->fEntries.Add(this);
127}
128
129
130// #pragma mark - ConditionVariable
131
132
133/*!	Initialization method for anonymous (unpublished) condition variables.
134*/
135void
136ConditionVariable::Init(const void* object, const char* objectType)
137{
138	fObject = object;
139	fObjectType = objectType;
140	new(&fEntries) EntryList;
141}
142
143
144void
145ConditionVariable::Publish(const void* object, const char* objectType)
146{
147	ASSERT(object != NULL);
148
149	fObject = object;
150	fObjectType = objectType;
151	new(&fEntries) EntryList;
152
153	MutexLocker locker(sConditionVariablesLock);
154
155	ASSERT(sConditionVariableHash.Lookup(object) == NULL);
156
157	sConditionVariableHash.InsertUnchecked(this);
158}
159
160
161void
162ConditionVariable::Unpublish()
163{
164	ASSERT(fObject != NULL);
165
166	MutexLocker locker(sConditionVariablesLock);
167
168	sConditionVariableHash.RemoveUnchecked(this);
169	fObject = NULL;
170	fObjectType = NULL;
171
172	if (!fEntries.IsEmpty())
173		_NotifyLocked(true, B_ENTRY_NOT_FOUND);
174}
175
176
177void
178ConditionVariable::Add(ConditionVariableEntry* entry)
179{
180	MutexLocker _(sConditionVariablesLock);
181	entry->AddToLockedVariable(this);
182}
183
184
185status_t
186ConditionVariable::Wait(uint32 flags, bigtime_t timeout)
187{
188	ConditionVariableEntry entry;
189	Add(&entry);
190	return entry.Wait(flags, timeout);
191}
192
193
194void
195ConditionVariable::_Notify(bool all, status_t result)
196{
197	MutexLocker locker(sConditionVariablesLock);
198
199	if (!fEntries.IsEmpty()) {
200		if (result > B_OK) {
201			panic("tried to notify with invalid result %" B_PRId32 "\n",
202				result);
203			result = B_ERROR;
204		}
205
206		_NotifyLocked(all, result);
207	}
208}
209
210
211/*! Called with interrupts disabled and the condition variable spinlock and
212	thread lock held.
213*/
214void
215ConditionVariable::_NotifyLocked(bool all, status_t result)
216{
217	// dequeue and wake up the blocked threads
218	while (ConditionVariableEntry* entry = fEntries.RemoveHead()) {
219		entry->fVariable = NULL;
220
221		if (entry->fWaitStatus <= 0)
222			continue;
223
224		if (entry->fWaitStatus == STATUS_WAITING)
225			_kern_unblock_thread(get_thread_id(entry->fThread), result);
226
227		entry->fWaitStatus = result;
228
229		if (!all)
230			break;
231	}
232}
233
234
235// #pragma mark -
236
237
238void
239condition_variable_init()
240{
241	status_t error = sConditionVariableHash.Init(kConditionVariableHashSize);
242	if (error != B_OK) {
243		panic("condition_variable_init(): Failed to init hash table: %s",
244			strerror(error));
245	}
246}
247