xref: /haiku/src/system/kernel/condition_variable.cpp (revision 9fba8251e92050556f19f1f45ab805cd1b60351a)
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 		_NotifyChecked(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::ListAll()
266 {
267 	kprintf("  variable      object (type)                waiting threads\n");
268 	kprintf("------------------------------------------------------------\n");
269 	ConditionVariableHash::Iterator it(&sConditionVariableHash);
270 	while (ConditionVariable* variable = it.Next()) {
271 		// count waiting threads
272 		int count = variable->fEntries.Count();
273 
274 		kprintf("%p  %p  %-20s %15d\n", variable, variable->fObject,
275 			variable->fObjectType, count);
276 	}
277 }
278 
279 
280 void
281 ConditionVariable::Dump() const
282 {
283 	kprintf("condition variable %p\n", this);
284 	kprintf("  object:  %p (%s)\n", fObject, fObjectType);
285 	kprintf("  threads:");
286 
287 	for (EntryList::ConstIterator it = fEntries.GetIterator();
288 		 ConditionVariableEntry* entry = it.Next();) {
289 		kprintf(" %ld", entry->fThread->id);
290 	}
291 	kprintf("\n");
292 }
293 
294 
295 void
296 ConditionVariable::_Notify(bool all, bool threadsLocked)
297 {
298 	InterruptsLocker _;
299 	SpinLocker threadLocker(threadsLocked ? NULL : &gThreadSpinlock);
300 	SpinLocker locker(sConditionVariablesLock);
301 
302 	if (!fEntries.IsEmpty())
303 		_NotifyChecked(all, B_OK);
304 }
305 
306 
307 /*! Called with interrupts disabled and the condition variable spinlock and
308 	thread lock held.
309 */
310 void
311 ConditionVariable::_NotifyChecked(bool all, status_t result)
312 {
313 	// dequeue and wake up the blocked threads
314 	while (ConditionVariableEntry* entry = fEntries.RemoveHead()) {
315 		entry->fVariable = NULL;
316 
317 		if (entry->fWaitStatus <= 0)
318 			continue;
319 
320 		if (entry->fWaitStatus == STATUS_WAITING)
321 			thread_unblock_locked(entry->fThread, result);
322 
323 		entry->fWaitStatus = result;
324 
325 		if (!all)
326 			break;
327 	}
328 }
329 
330 
331 // #pragma mark -
332 
333 
334 void
335 condition_variable_init()
336 {
337 	new(&sConditionVariableHash) ConditionVariableHash;
338 
339 	status_t error = sConditionVariableHash.Init(kConditionVariableHashSize);
340 	if (error != B_OK) {
341 		panic("condition_variable_init(): Failed to init hash table: %s",
342 			strerror(error));
343 	}
344 
345 	add_debugger_command_etc("cvar", &dump_condition_variable,
346 		"Dump condition variable info",
347 		"<address>\n"
348 		"Prints info for the specified condition variable.\n"
349 		"  <address>  - Address of the condition variable or the object it is\n"
350 		"               associated with.\n", 0);
351 	add_debugger_command_etc("cvars", &list_condition_variables,
352 		"List condition variables",
353 		"\n"
354 		"Lists all existing condition variables\n", 0);
355 }
356