xref: /haiku/src/system/kernel/condition_variable.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
1 /*
2  * Copyright 2007-2008, Ingo Weinhold, bonefish@cs.tu-berlin.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 <debug.h>
13 #include <kscheduler.h>
14 #include <ksignal.h>
15 #include <int.h>
16 #include <listeners.h>
17 #include <scheduling_analysis.h>
18 #include <thread.h>
19 #include <util/AutoLock.h>
20 
21 
22 #define STATUS_ADDED	1
23 #define STATUS_WAITING	2
24 
25 
26 static const int kConditionVariableHashSize = 512;
27 
28 
29 struct ConditionVariableHashDefinition {
30 	typedef const void* KeyType;
31 	typedef	ConditionVariable ValueType;
32 
33 	size_t HashKey(const void* key) const
34 		{ return (size_t)key; }
35 	size_t Hash(ConditionVariable* variable) const
36 		{ return (size_t)variable->fObject; }
37 	bool Compare(const void* key, ConditionVariable* variable) const
38 		{ return key == variable->fObject; }
39 	ConditionVariable*& GetLink(ConditionVariable* variable) const
40 		{ return variable->fNext; }
41 };
42 
43 typedef BOpenHashTable<ConditionVariableHashDefinition> ConditionVariableHash;
44 static ConditionVariableHash sConditionVariableHash;
45 static spinlock sConditionVariablesLock;
46 
47 
48 static int
49 list_condition_variables(int argc, char** argv)
50 {
51 	ConditionVariable::ListAll();
52 	return 0;
53 }
54 
55 
56 static int
57 dump_condition_variable(int argc, char** argv)
58 {
59 	if (argc != 2) {
60 		print_debugger_command_usage(argv[0]);
61 		return 0;
62 	}
63 
64 	addr_t address = parse_expression(argv[1]);
65 	if (address == 0)
66 		return 0;
67 
68 	ConditionVariable* variable = sConditionVariableHash.Lookup((void*)address);
69 
70 	if (variable == NULL) {
71 		// It must be a direct pointer to a condition variable.
72 		variable = (ConditionVariable*)address;
73 	}
74 
75 	if (variable != NULL) {
76 		variable->Dump();
77 
78 		set_debug_variable("_cvar", (addr_t)variable);
79 		set_debug_variable("_object", (addr_t)variable->Object());
80 
81 	} else
82 		kprintf("no condition variable at or with key %p\n", (void*)address);
83 
84 	return 0;
85 }
86 
87 
88 // #pragma mark - ConditionVariableEntry
89 
90 
91 bool
92 ConditionVariableEntry::Add(const void* object)
93 {
94 	ASSERT(object != NULL);
95 
96 	fThread = thread_get_current_thread();
97 
98 	InterruptsSpinLocker _(sConditionVariablesLock);
99 
100 	fVariable = sConditionVariableHash.Lookup(object);
101 
102 	if (fVariable == NULL) {
103 		fWaitStatus = B_ENTRY_NOT_FOUND;
104 		return false;
105 	}
106 
107 	fWaitStatus = STATUS_ADDED;
108 	fVariable->fEntries.Add(this);
109 
110 	return true;
111 }
112 
113 
114 status_t
115 ConditionVariableEntry::Wait(uint32 flags, bigtime_t timeout)
116 {
117 	if (!are_interrupts_enabled()) {
118 		panic("ConditionVariableEntry::Wait() called with interrupts "
119 			"disabled, entry: %p, variable: %p", this, fVariable);
120 		return B_ERROR;
121 	}
122 
123 	InterruptsLocker _;
124 
125 	SpinLocker conditionLocker(sConditionVariablesLock);
126 
127 	if (fVariable == NULL)
128 		return fWaitStatus;
129 
130 	thread_prepare_to_block(fThread, flags,
131 		THREAD_BLOCK_TYPE_CONDITION_VARIABLE, fVariable);
132 
133 	fWaitStatus = STATUS_WAITING;
134 
135 	conditionLocker.Unlock();
136 
137 	SpinLocker threadLocker(gThreadSpinlock);
138 
139 	status_t error;
140 	if ((flags & (B_RELATIVE_TIMEOUT | B_ABSOLUTE_TIMEOUT)) != 0)
141 		error = thread_block_with_timeout_locked(flags, timeout);
142 	else
143 		error = thread_block_locked(thread_get_current_thread());
144 	threadLocker.Unlock();
145 
146 	conditionLocker.Lock();
147 
148 	// remove entry from variable, if not done yet
149 	if (fVariable != NULL) {
150 		fVariable->fEntries.Remove(this);
151 		fVariable = NULL;
152 	}
153 
154 	return error;
155 }
156 
157 
158 status_t
159 ConditionVariableEntry::Wait(const void* object, uint32 flags,
160 	bigtime_t timeout)
161 {
162 	if (Add(object))
163 		return Wait(flags, timeout);
164 	return B_ENTRY_NOT_FOUND;
165 }
166 
167 
168 inline void
169 ConditionVariableEntry::AddToVariable(ConditionVariable* variable)
170 {
171 	fThread = thread_get_current_thread();
172 
173 	InterruptsSpinLocker _(sConditionVariablesLock);
174 
175 	fVariable = variable;
176 	fWaitStatus = STATUS_ADDED;
177 	fVariable->fEntries.Add(this);
178 }
179 
180 
181 // #pragma mark - ConditionVariable
182 
183 
184 /*!	Initialization method for anonymous (unpublished) condition variables.
185 */
186 void
187 ConditionVariable::Init(const void* object, const char* objectType)
188 {
189 	fObject = object;
190 	fObjectType = objectType;
191 	new(&fEntries) EntryList;
192 
193 	T_SCHEDULING_ANALYSIS(InitConditionVariable(this, object, objectType));
194 	NotifyWaitObjectListeners(&WaitObjectListener::ConditionVariableInitialized,
195 		this);
196 }
197 
198 
199 void
200 ConditionVariable::Publish(const void* object, const char* objectType)
201 {
202 	ASSERT(object != NULL);
203 
204 	fObject = object;
205 	fObjectType = objectType;
206 	new(&fEntries) EntryList;
207 
208 	T_SCHEDULING_ANALYSIS(InitConditionVariable(this, object, objectType));
209 	NotifyWaitObjectListeners(&WaitObjectListener::ConditionVariableInitialized,
210 		this);
211 
212 	InterruptsLocker _;
213 	SpinLocker locker(sConditionVariablesLock);
214 
215 	ASSERT_PRINT(sConditionVariableHash.Lookup(object) == NULL,
216 		"condition variable: %p\n", sConditionVariableHash.Lookup(object));
217 
218 	sConditionVariableHash.InsertUnchecked(this);
219 }
220 
221 
222 void
223 ConditionVariable::Unpublish(bool threadsLocked)
224 {
225 	ASSERT(fObject != NULL);
226 
227 	InterruptsLocker _;
228 	SpinLocker threadLocker(threadsLocked ? NULL : &gThreadSpinlock);
229 	SpinLocker locker(sConditionVariablesLock);
230 
231 #if KDEBUG
232 	ConditionVariable* variable = sConditionVariableHash.Lookup(fObject);
233 	if (variable != this) {
234 		panic("Condition variable %p not published, found: %p", this, variable);
235 		return;
236 	}
237 #endif
238 
239 	sConditionVariableHash.RemoveUnchecked(this);
240 	fObject = NULL;
241 	fObjectType = NULL;
242 
243 	if (!fEntries.IsEmpty())
244 		_NotifyLocked(true, B_ENTRY_NOT_FOUND);
245 }
246 
247 
248 void
249 ConditionVariable::Add(ConditionVariableEntry* entry)
250 {
251 	entry->AddToVariable(this);
252 }
253 
254 
255 status_t
256 ConditionVariable::Wait(uint32 flags, bigtime_t timeout)
257 {
258 	ConditionVariableEntry entry;
259 	Add(&entry);
260 	return entry.Wait(flags, timeout);
261 }
262 
263 
264 /*static*/ void
265 ConditionVariable::NotifyOne(const void* object, bool threadsLocked,
266 	status_t result)
267 {
268 	InterruptsSpinLocker locker(sConditionVariablesLock);
269 	ConditionVariable* variable = sConditionVariableHash.Lookup(object);
270 	locker.Unlock();
271 	if (variable == NULL)
272 		return;
273 
274 	variable->NotifyOne(threadsLocked, result);
275 }
276 
277 
278 /*static*/ void
279 ConditionVariable::NotifyAll(const void* object,
280 	bool threadsLocked, status_t result)
281 {
282 	InterruptsSpinLocker locker(sConditionVariablesLock);
283 	ConditionVariable* variable = sConditionVariableHash.Lookup(object);
284 	locker.Unlock();
285 	if (variable == NULL)
286 		return;
287 
288 	variable->NotifyAll(threadsLocked, result);
289 }
290 
291 
292 /*static*/ void
293 ConditionVariable::ListAll()
294 {
295 	kprintf("  variable      object (type)                waiting threads\n");
296 	kprintf("------------------------------------------------------------\n");
297 	ConditionVariableHash::Iterator it(&sConditionVariableHash);
298 	while (ConditionVariable* variable = it.Next()) {
299 		// count waiting threads
300 		int count = variable->fEntries.Count();
301 
302 		kprintf("%p  %p  %-20s %15d\n", variable, variable->fObject,
303 			variable->fObjectType, count);
304 	}
305 }
306 
307 
308 void
309 ConditionVariable::Dump() const
310 {
311 	kprintf("condition variable %p\n", this);
312 	kprintf("  object:  %p (%s)\n", fObject, fObjectType);
313 	kprintf("  threads:");
314 
315 	for (EntryList::ConstIterator it = fEntries.GetIterator();
316 		 ConditionVariableEntry* entry = it.Next();) {
317 		kprintf(" %ld", entry->fThread->id);
318 	}
319 	kprintf("\n");
320 }
321 
322 
323 void
324 ConditionVariable::_Notify(bool all, bool threadsLocked, status_t result)
325 {
326 	InterruptsLocker _;
327 	SpinLocker threadLocker(threadsLocked ? NULL : &gThreadSpinlock);
328 	SpinLocker locker(sConditionVariablesLock);
329 
330 	if (!fEntries.IsEmpty()) {
331 		if (result > B_OK) {
332 			panic("tried to notify with invalid result %ld\n", result);
333 			result = B_ERROR;
334 		}
335 
336 		_NotifyLocked(all, result);
337 	}
338 }
339 
340 
341 /*! Called with interrupts disabled and the condition variable spinlock and
342 	thread lock held.
343 */
344 void
345 ConditionVariable::_NotifyLocked(bool all, status_t result)
346 {
347 	// dequeue and wake up the blocked threads
348 	while (ConditionVariableEntry* entry = fEntries.RemoveHead()) {
349 		entry->fVariable = NULL;
350 
351 		if (entry->fWaitStatus <= 0)
352 			continue;
353 
354 		if (entry->fWaitStatus == STATUS_WAITING)
355 			thread_unblock_locked(entry->fThread, result);
356 
357 		entry->fWaitStatus = result;
358 
359 		if (!all)
360 			break;
361 	}
362 }
363 
364 
365 // #pragma mark -
366 
367 
368 void
369 condition_variable_init()
370 {
371 	new(&sConditionVariableHash) ConditionVariableHash;
372 
373 	status_t error = sConditionVariableHash.Init(kConditionVariableHashSize);
374 	if (error != B_OK) {
375 		panic("condition_variable_init(): Failed to init hash table: %s",
376 			strerror(error));
377 	}
378 
379 	add_debugger_command_etc("cvar", &dump_condition_variable,
380 		"Dump condition variable info",
381 		"<address>\n"
382 		"Prints info for the specified condition variable.\n"
383 		"  <address>  - Address of the condition variable or the object it is\n"
384 		"               associated with.\n", 0);
385 	add_debugger_command_etc("cvars", &list_condition_variables,
386 		"List condition variables",
387 		"\n"
388 		"Lists all existing condition variables\n", 0);
389 }
390