xref: /haiku/src/system/kernel/condition_variable.cpp (revision a127b88ecbfab58f64944c98aa47722a18e363b2)
1 /*
2  * Copyright 2007-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Copyright 2019, Haiku, Inc. All rights reserved.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 #include <condition_variable.h>
8 
9 #include <new>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #include <debug.h>
14 #include <kscheduler.h>
15 #include <ksignal.h>
16 #include <int.h>
17 #include <listeners.h>
18 #include <scheduling_analysis.h>
19 #include <thread.h>
20 #include <util/AutoLock.h>
21 #include <util/atomic.h>
22 
23 
24 #define STATUS_ADDED	1
25 #define STATUS_WAITING	2
26 
27 
28 static const int kConditionVariableHashSize = 512;
29 
30 
31 struct ConditionVariableHashDefinition {
32 	typedef const void* KeyType;
33 	typedef	ConditionVariable ValueType;
34 
35 	size_t HashKey(const void* key) const
36 		{ return (size_t)key; }
37 	size_t Hash(ConditionVariable* variable) const
38 		{ return (size_t)variable->fObject; }
39 	bool Compare(const void* key, ConditionVariable* variable) const
40 		{ return key == variable->fObject; }
41 	ConditionVariable*& GetLink(ConditionVariable* variable) const
42 		{ return variable->fNext; }
43 };
44 
45 typedef BOpenHashTable<ConditionVariableHashDefinition> ConditionVariableHash;
46 static ConditionVariableHash sConditionVariableHash;
47 static rw_spinlock sConditionVariableHashLock;
48 
49 
50 static int
51 list_condition_variables(int argc, char** argv)
52 {
53 	ConditionVariable::ListAll();
54 	return 0;
55 }
56 
57 
58 static int
59 dump_condition_variable(int argc, char** argv)
60 {
61 	if (argc != 2) {
62 		print_debugger_command_usage(argv[0]);
63 		return 0;
64 	}
65 
66 	addr_t address = parse_expression(argv[1]);
67 	if (address == 0)
68 		return 0;
69 
70 	ConditionVariable* variable = sConditionVariableHash.Lookup((void*)address);
71 
72 	if (variable == NULL) {
73 		// It must be a direct pointer to a condition variable.
74 		variable = (ConditionVariable*)address;
75 	}
76 
77 	if (variable != NULL) {
78 		variable->Dump();
79 
80 		set_debug_variable("_cvar", (addr_t)variable);
81 		set_debug_variable("_object", (addr_t)variable->Object());
82 
83 	} else
84 		kprintf("no condition variable at or with key %p\n", (void*)address);
85 
86 	return 0;
87 }
88 
89 
90 // #pragma mark - ConditionVariableEntry
91 
92 
93 ConditionVariableEntry::ConditionVariableEntry()
94 	: fVariable(NULL)
95 {
96 }
97 
98 
99 ConditionVariableEntry::~ConditionVariableEntry()
100 {
101 	// We can use an "unsafe" non-atomic access of fVariable here, since we only
102 	// care whether it is non-NULL, not what its specific value is.
103 	if (fVariable != NULL)
104 		_RemoveFromVariable();
105 }
106 
107 
108 bool
109 ConditionVariableEntry::Add(const void* object)
110 {
111 	ASSERT(object != NULL);
112 
113 	InterruptsLocker _;
114 	ReadSpinLocker hashLocker(sConditionVariableHashLock);
115 
116 	ConditionVariable* variable = sConditionVariableHash.Lookup(object);
117 
118 	if (variable == NULL) {
119 		fWaitStatus = B_ENTRY_NOT_FOUND;
120 		return false;
121 	}
122 
123 	SpinLocker variableLocker(variable->fLock);
124 	hashLocker.Unlock();
125 
126 	_AddToLockedVariable(variable);
127 
128 	return true;
129 }
130 
131 
132 ConditionVariable*
133 ConditionVariableEntry::Variable() const
134 {
135 	return atomic_pointer_get(&fVariable);
136 }
137 
138 
139 inline void
140 ConditionVariableEntry::_AddToLockedVariable(ConditionVariable* variable)
141 {
142 	ASSERT(fVariable == NULL);
143 
144 	fThread = thread_get_current_thread();
145 	fVariable = variable;
146 	fWaitStatus = STATUS_ADDED;
147 	fVariable->fEntries.Add(this);
148 	atomic_add(&fVariable->fEntriesCount, 1);
149 }
150 
151 
152 void
153 ConditionVariableEntry::_RemoveFromVariable()
154 {
155 	// This section is critical because it can race with _NotifyLocked on the
156 	// variable's thread, so we must not be interrupted during it.
157 	InterruptsLocker _;
158 
159 	ConditionVariable* variable = atomic_pointer_get(&fVariable);
160 	if (atomic_pointer_get_and_set(&fThread, (Thread*)NULL) == NULL) {
161 		// If fThread was already NULL, that means the variable is already
162 		// in the process of clearing us out (or already has finished doing so.)
163 		// We thus cannot access fVariable, and must spin until it is cleared.
164 		int32 tries = 0;
165 		while (atomic_pointer_get(&fVariable) != NULL) {
166 			tries++;
167 			if ((tries % 10000) == 0)
168 				dprintf("variable pointer was not unset for a long time!\n");
169 			cpu_pause();
170 		}
171 
172 		return;
173 	}
174 
175 	while (true) {
176 		if (atomic_pointer_get(&fVariable) == NULL) {
177 			// The variable must have cleared us out. Acknowledge this and return.
178 			atomic_add(&variable->fEntriesCount, -1);
179 			return;
180 		}
181 
182 		// There is of course a small race between checking the pointer and then
183 		// the try_acquire in which the variable might clear out our fVariable.
184 		// However, in the case where we were the ones to clear fThread, the
185 		// variable will notice that and then wait for us to acknowledge the
186 		// removal by decrementing fEntriesCount, as we do above; and until
187 		// we do that, we may validly use our cached pointer to the variable.
188 		if (try_acquire_spinlock(&variable->fLock))
189 			break;
190 	}
191 
192 	// We now hold the variable's lock. Remove ourselves.
193 	if (fVariable->fEntries.Contains(this))
194 		fVariable->fEntries.Remove(this);
195 
196 	atomic_pointer_set(&fVariable, (ConditionVariable*)NULL);
197 	atomic_add(&variable->fEntriesCount, -1);
198 	release_spinlock(&variable->fLock);
199 }
200 
201 
202 status_t
203 ConditionVariableEntry::Wait(uint32 flags, bigtime_t timeout)
204 {
205 #if KDEBUG
206 	if (!are_interrupts_enabled()) {
207 		panic("ConditionVariableEntry::Wait() called with interrupts "
208 			"disabled, entry: %p, variable: %p", this, fVariable);
209 		return B_ERROR;
210 	}
211 #endif
212 
213 	ConditionVariable* variable = atomic_pointer_get(&fVariable);
214 	if (variable == NULL)
215 		return fWaitStatus;
216 
217 	if ((flags & B_RELATIVE_TIMEOUT) != 0 && timeout <= 0) {
218 		_RemoveFromVariable();
219 		return B_WOULD_BLOCK;
220 	}
221 
222 	InterruptsLocker _;
223 	SpinLocker schedulerLocker(thread_get_current_thread()->scheduler_lock);
224 
225 	if (fWaitStatus <= 0)
226 		return fWaitStatus;
227 	fWaitStatus = STATUS_WAITING;
228 
229 	thread_prepare_to_block(thread_get_current_thread(), flags,
230 		THREAD_BLOCK_TYPE_CONDITION_VARIABLE, variable);
231 
232 	schedulerLocker.Unlock();
233 
234 	status_t error;
235 	if ((flags & (B_RELATIVE_TIMEOUT | B_ABSOLUTE_TIMEOUT)) != 0)
236 		error = thread_block_with_timeout(flags, timeout);
237 	else
238 		error = thread_block();
239 
240 	_RemoveFromVariable();
241 	return error;
242 }
243 
244 
245 status_t
246 ConditionVariableEntry::Wait(const void* object, uint32 flags,
247 	bigtime_t timeout)
248 {
249 	if (Add(object))
250 		return Wait(flags, timeout);
251 	return B_ENTRY_NOT_FOUND;
252 }
253 
254 
255 // #pragma mark - ConditionVariable
256 
257 
258 /*!	Initialization method for anonymous (unpublished) condition variables.
259 */
260 void
261 ConditionVariable::Init(const void* object, const char* objectType)
262 {
263 	fObject = object;
264 	fObjectType = objectType;
265 	new(&fEntries) EntryList;
266 	fEntriesCount = 0;
267 	B_INITIALIZE_SPINLOCK(&fLock);
268 
269 	T_SCHEDULING_ANALYSIS(InitConditionVariable(this, object, objectType));
270 	NotifyWaitObjectListeners(&WaitObjectListener::ConditionVariableInitialized,
271 		this);
272 }
273 
274 
275 void
276 ConditionVariable::Publish(const void* object, const char* objectType)
277 {
278 	ASSERT(object != NULL);
279 
280 	Init(object, objectType);
281 
282 	InterruptsWriteSpinLocker _(sConditionVariableHashLock);
283 
284 	ASSERT_PRINT(sConditionVariableHash.Lookup(object) == NULL,
285 		"condition variable: %p\n", sConditionVariableHash.Lookup(object));
286 
287 	sConditionVariableHash.InsertUnchecked(this);
288 }
289 
290 
291 void
292 ConditionVariable::Unpublish()
293 {
294 	ASSERT(fObject != NULL);
295 
296 	InterruptsLocker _;
297 	WriteSpinLocker hashLocker(sConditionVariableHashLock);
298 	SpinLocker selfLocker(fLock);
299 
300 #if KDEBUG
301 	ConditionVariable* variable = sConditionVariableHash.Lookup(fObject);
302 	if (variable != this) {
303 		panic("Condition variable %p not published, found: %p", this, variable);
304 		return;
305 	}
306 #endif
307 
308 	sConditionVariableHash.RemoveUnchecked(this);
309 	fObject = NULL;
310 	fObjectType = NULL;
311 
312 	hashLocker.Unlock();
313 
314 	if (!fEntries.IsEmpty())
315 		_NotifyLocked(true, B_ENTRY_NOT_FOUND);
316 }
317 
318 
319 void
320 ConditionVariable::Add(ConditionVariableEntry* entry)
321 {
322 	InterruptsSpinLocker _(fLock);
323 	entry->_AddToLockedVariable(this);
324 }
325 
326 
327 status_t
328 ConditionVariable::Wait(uint32 flags, bigtime_t timeout)
329 {
330 	ConditionVariableEntry entry;
331 	Add(&entry);
332 	return entry.Wait(flags, timeout);
333 }
334 
335 
336 status_t
337 ConditionVariable::Wait(mutex* lock, uint32 flags, bigtime_t timeout)
338 {
339 	ConditionVariableEntry entry;
340 	Add(&entry);
341 	mutex_unlock(lock);
342 	status_t res = entry.Wait(flags, timeout);
343 	mutex_lock(lock);
344 	return res;
345 }
346 
347 
348 status_t
349 ConditionVariable::Wait(recursive_lock* lock, uint32 flags, bigtime_t timeout)
350 {
351 	ConditionVariableEntry entry;
352 	Add(&entry);
353 	int32 recursion = recursive_lock_get_recursion(lock);
354 
355 	for (int32 i = 0; i < recursion; i++)
356 		recursive_lock_unlock(lock);
357 
358 	status_t res = entry.Wait(flags, timeout);
359 
360 	for (int32 i = 0; i < recursion; i++)
361 		recursive_lock_lock(lock);
362 
363 	return res;
364 }
365 
366 
367 /*static*/ void
368 ConditionVariable::NotifyOne(const void* object, status_t result)
369 {
370 	_Notify(object, false, result);
371 }
372 
373 
374 /*static*/ void
375 ConditionVariable::NotifyAll(const void* object, status_t result)
376 {
377 	_Notify(object, true, result);
378 }
379 
380 
381 /*static*/ void
382 ConditionVariable::_Notify(const void* object, bool all, status_t result)
383 {
384 	InterruptsLocker ints;
385 	ReadSpinLocker hashLocker(sConditionVariableHashLock);
386 	ConditionVariable* variable = sConditionVariableHash.Lookup(object);
387 	if (variable == NULL)
388 		return;
389 	SpinLocker variableLocker(variable->fLock);
390 	hashLocker.Unlock();
391 
392 	variable->_NotifyLocked(all, result);
393 }
394 
395 
396 void
397 ConditionVariable::_Notify(bool all, status_t result)
398 {
399 	InterruptsSpinLocker _(fLock);
400 
401 	if (!fEntries.IsEmpty()) {
402 		if (result > B_OK) {
403 			panic("tried to notify with invalid result %" B_PRId32 "\n", result);
404 			result = B_ERROR;
405 		}
406 
407 		_NotifyLocked(all, result);
408 	}
409 }
410 
411 
412 /*! Called with interrupts disabled and the condition variable's spinlock held.
413  */
414 void
415 ConditionVariable::_NotifyLocked(bool all, status_t result)
416 {
417 	// Dequeue and wake up the blocked threads.
418 	while (ConditionVariableEntry* entry = fEntries.RemoveHead()) {
419 		Thread* thread = atomic_pointer_get_and_set(&entry->fThread, (Thread*)NULL);
420 		if (thread == NULL) {
421 			// The entry must be in the process of trying to remove itself from us.
422 			// Clear its variable and wait for it to acknowledge this in fEntriesCount,
423 			// as it is the one responsible for decrementing that.
424 			const int32 oldCount = atomic_get(&fEntriesCount);
425 			atomic_pointer_set(&entry->fVariable, (ConditionVariable*)NULL);
426 
427 			// As fEntriesCount is only modified while our lock is held, nothing else
428 			// will modify it while we are spinning, since we hold it at present.
429 			int32 tries = 0;
430 			while (atomic_get(&fEntriesCount) == oldCount) {
431 				tries++;
432 				if ((tries % 10000) == 0)
433 					dprintf("entries count was not decremented for a long time!\n");
434 				cpu_pause();
435 			}
436 		} else {
437 			SpinLocker schedulerLocker(thread->scheduler_lock);
438 			status_t lastWaitStatus = entry->fWaitStatus;
439 			entry->fWaitStatus = result;
440 			if (lastWaitStatus == STATUS_WAITING && thread->state != B_THREAD_WAITING) {
441 				// The thread is not in B_THREAD_WAITING state, so we must unblock it early,
442 				// in case it tries to re-block itself immediately after we unset fVariable.
443 				thread_unblock_locked(thread, result);
444 				lastWaitStatus = result;
445 			}
446 
447 			// No matter what the thread is doing, as we were the ones to clear its
448 			// fThread, so we are the ones responsible for decrementing fEntriesCount.
449 			// (We may not validly access the entry once we unset its fVariable.)
450 			atomic_pointer_set(&entry->fVariable, (ConditionVariable*)NULL);
451 			atomic_add(&fEntriesCount, -1);
452 
453 			// If the thread was in B_THREAD_WAITING state, we unblock it after unsetting
454 			// fVariable, because otherwise it will wake up before thread_unblock returns
455 			// and spin while waiting for us to do so.
456 			if (lastWaitStatus == STATUS_WAITING)
457 				thread_unblock_locked(thread, result);
458 		}
459 
460 		if (!all)
461 			break;
462 	}
463 }
464 
465 
466 /*static*/ void
467 ConditionVariable::ListAll()
468 {
469 	kprintf("  variable      object (type)                waiting threads\n");
470 	kprintf("------------------------------------------------------------\n");
471 	ConditionVariableHash::Iterator it(&sConditionVariableHash);
472 	while (ConditionVariable* variable = it.Next()) {
473 		// count waiting threads
474 		int count = variable->fEntries.Count();
475 
476 		kprintf("%p  %p  %-20s %15d\n", variable, variable->fObject,
477 			variable->fObjectType, count);
478 	}
479 }
480 
481 
482 void
483 ConditionVariable::Dump() const
484 {
485 	kprintf("condition variable %p\n", this);
486 	kprintf("  object:  %p (%s)\n", fObject, fObjectType);
487 	kprintf("  threads:");
488 
489 	for (EntryList::ConstIterator it = fEntries.GetIterator();
490 		 ConditionVariableEntry* entry = it.Next();) {
491 		kprintf(" %" B_PRId32, entry->fThread->id);
492 	}
493 	kprintf("\n");
494 }
495 
496 
497 // #pragma mark -
498 
499 
500 void
501 condition_variable_init()
502 {
503 	new(&sConditionVariableHash) ConditionVariableHash;
504 
505 	status_t error = sConditionVariableHash.Init(kConditionVariableHashSize);
506 	if (error != B_OK) {
507 		panic("condition_variable_init(): Failed to init hash table: %s",
508 			strerror(error));
509 	}
510 
511 	add_debugger_command_etc("cvar", &dump_condition_variable,
512 		"Dump condition variable info",
513 		"<address>\n"
514 		"Prints info for the specified condition variable.\n"
515 		"  <address>  - Address of the condition variable or the object it is\n"
516 		"               associated with.\n", 0);
517 	add_debugger_command_etc("cvars", &list_condition_variables,
518 		"List condition variables",
519 		"\n"
520 		"Lists all existing condition variables\n", 0);
521 }
522