xref: /haiku/src/system/kernel/condition_variable.cpp (revision 83b1a68c52ba3e0e8796282759f694b7fdddf06d)
1 /*
2  * Copyright 2007-2011, 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 <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 	status_t error;
138 	if ((flags & (B_RELATIVE_TIMEOUT | B_ABSOLUTE_TIMEOUT)) != 0)
139 		error = thread_block_with_timeout(flags, timeout);
140 	else
141 		error = thread_block();
142 
143 	conditionLocker.Lock();
144 
145 	// remove entry from variable, if not done yet
146 	if (fVariable != NULL) {
147 		fVariable->fEntries.Remove(this);
148 		fVariable = NULL;
149 	}
150 
151 	return error;
152 }
153 
154 
155 status_t
156 ConditionVariableEntry::Wait(const void* object, uint32 flags,
157 	bigtime_t timeout)
158 {
159 	if (Add(object))
160 		return Wait(flags, timeout);
161 	return B_ENTRY_NOT_FOUND;
162 }
163 
164 
165 inline void
166 ConditionVariableEntry::AddToVariable(ConditionVariable* variable)
167 {
168 	fThread = thread_get_current_thread();
169 
170 	InterruptsSpinLocker _(sConditionVariablesLock);
171 
172 	fVariable = variable;
173 	fWaitStatus = STATUS_ADDED;
174 	fVariable->fEntries.Add(this);
175 }
176 
177 
178 // #pragma mark - ConditionVariable
179 
180 
181 /*!	Initialization method for anonymous (unpublished) condition variables.
182 */
183 void
184 ConditionVariable::Init(const void* object, const char* objectType)
185 {
186 	fObject = object;
187 	fObjectType = objectType;
188 	new(&fEntries) EntryList;
189 
190 	T_SCHEDULING_ANALYSIS(InitConditionVariable(this, object, objectType));
191 	NotifyWaitObjectListeners(&WaitObjectListener::ConditionVariableInitialized,
192 		this);
193 }
194 
195 
196 void
197 ConditionVariable::Publish(const void* object, const char* objectType)
198 {
199 	ASSERT(object != NULL);
200 
201 	fObject = object;
202 	fObjectType = objectType;
203 	new(&fEntries) EntryList;
204 
205 	T_SCHEDULING_ANALYSIS(InitConditionVariable(this, object, objectType));
206 	NotifyWaitObjectListeners(&WaitObjectListener::ConditionVariableInitialized,
207 		this);
208 
209 	InterruptsLocker _;
210 	SpinLocker locker(sConditionVariablesLock);
211 
212 	ASSERT_PRINT(sConditionVariableHash.Lookup(object) == NULL,
213 		"condition variable: %p\n", sConditionVariableHash.Lookup(object));
214 
215 	sConditionVariableHash.InsertUnchecked(this);
216 }
217 
218 
219 void
220 ConditionVariable::Unpublish()
221 {
222 	ASSERT(fObject != NULL);
223 
224 	InterruptsSpinLocker locker(sConditionVariablesLock);
225 
226 #if KDEBUG
227 	ConditionVariable* variable = sConditionVariableHash.Lookup(fObject);
228 	if (variable != this) {
229 		panic("Condition variable %p not published, found: %p", this, variable);
230 		return;
231 	}
232 #endif
233 
234 	sConditionVariableHash.RemoveUnchecked(this);
235 	fObject = NULL;
236 	fObjectType = NULL;
237 
238 	if (!fEntries.IsEmpty())
239 		_NotifyLocked(true, B_ENTRY_NOT_FOUND);
240 }
241 
242 
243 void
244 ConditionVariable::Add(ConditionVariableEntry* entry)
245 {
246 	entry->AddToVariable(this);
247 }
248 
249 
250 status_t
251 ConditionVariable::Wait(uint32 flags, bigtime_t timeout)
252 {
253 	ConditionVariableEntry entry;
254 	Add(&entry);
255 	return entry.Wait(flags, timeout);
256 }
257 
258 
259 /*static*/ void
260 ConditionVariable::NotifyOne(const void* object, status_t result)
261 {
262 	InterruptsSpinLocker locker(sConditionVariablesLock);
263 	ConditionVariable* variable = sConditionVariableHash.Lookup(object);
264 	locker.Unlock();
265 	if (variable == NULL)
266 		return;
267 
268 	variable->NotifyOne(result);
269 }
270 
271 
272 /*static*/ void
273 ConditionVariable::NotifyAll(const void* object, status_t result)
274 {
275 	InterruptsSpinLocker locker(sConditionVariablesLock);
276 	ConditionVariable* variable = sConditionVariableHash.Lookup(object);
277 	locker.Unlock();
278 	if (variable == NULL)
279 		return;
280 
281 	variable->NotifyAll(result);
282 }
283 
284 
285 /*static*/ void
286 ConditionVariable::ListAll()
287 {
288 	kprintf("  variable      object (type)                waiting threads\n");
289 	kprintf("------------------------------------------------------------\n");
290 	ConditionVariableHash::Iterator it(&sConditionVariableHash);
291 	while (ConditionVariable* variable = it.Next()) {
292 		// count waiting threads
293 		int count = variable->fEntries.Count();
294 
295 		kprintf("%p  %p  %-20s %15d\n", variable, variable->fObject,
296 			variable->fObjectType, count);
297 	}
298 }
299 
300 
301 void
302 ConditionVariable::Dump() const
303 {
304 	kprintf("condition variable %p\n", this);
305 	kprintf("  object:  %p (%s)\n", fObject, fObjectType);
306 	kprintf("  threads:");
307 
308 	for (EntryList::ConstIterator it = fEntries.GetIterator();
309 		 ConditionVariableEntry* entry = it.Next();) {
310 		kprintf(" %" B_PRId32, entry->fThread->id);
311 	}
312 	kprintf("\n");
313 }
314 
315 
316 void
317 ConditionVariable::_Notify(bool all, status_t result)
318 {
319 	InterruptsSpinLocker locker(sConditionVariablesLock);
320 
321 	if (!fEntries.IsEmpty()) {
322 		if (result > B_OK) {
323 			panic("tried to notify with invalid result %" B_PRId32 "\n", result);
324 			result = B_ERROR;
325 		}
326 
327 		_NotifyLocked(all, result);
328 	}
329 }
330 
331 
332 /*! Called with interrupts disabled and the condition variable spinlock and
333 	scheduler lock held.
334 */
335 void
336 ConditionVariable::_NotifyLocked(bool all, status_t result)
337 {
338 	// dequeue and wake up the blocked threads
339 	while (ConditionVariableEntry* entry = fEntries.RemoveHead()) {
340 		entry->fVariable = NULL;
341 
342 		if (entry->fWaitStatus <= 0)
343 			continue;
344 
345 		if (entry->fWaitStatus == STATUS_WAITING) {
346 			SpinLocker _(entry->fThread->scheduler_lock);
347 			thread_unblock_locked(entry->fThread, result);
348 		}
349 
350 		entry->fWaitStatus = result;
351 
352 		if (!all)
353 			break;
354 	}
355 }
356 
357 
358 // #pragma mark -
359 
360 
361 void
362 condition_variable_init()
363 {
364 	new(&sConditionVariableHash) ConditionVariableHash;
365 
366 	status_t error = sConditionVariableHash.Init(kConditionVariableHashSize);
367 	if (error != B_OK) {
368 		panic("condition_variable_init(): Failed to init hash table: %s",
369 			strerror(error));
370 	}
371 
372 	add_debugger_command_etc("cvar", &dump_condition_variable,
373 		"Dump condition variable info",
374 		"<address>\n"
375 		"Prints info for the specified condition variable.\n"
376 		"  <address>  - Address of the condition variable or the object it is\n"
377 		"               associated with.\n", 0);
378 	add_debugger_command_etc("cvars", &list_condition_variables,
379 		"List condition variables",
380 		"\n"
381 		"Lists all existing condition variables\n", 0);
382 }
383