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