xref: /haiku/src/servers/app/MultiLocker.cpp (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
1 /*
2  * Copyright 2005-2009, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Copyright 1999, Be Incorporated.   All Rights Reserved.
6  * This file may be used under the terms of the Be Sample Code License.
7  */
8 
9 
10 #include "MultiLocker.h"
11 
12 #include <Debug.h>
13 #include <Errors.h>
14 #include <OS.h>
15 
16 
17 #define TIMING	MULTI_LOCKER_TIMING
18 #ifndef DEBUG
19 #	define DEBUG	MULTI_LOCKER_DEBUG
20 #endif
21 
22 
23 const int32 LARGE_NUMBER = 100000;
24 
25 
26 MultiLocker::MultiLocker(const char* baseName)
27 	:
28 #if DEBUG
29 	fDebugArray(NULL),
30 	fMaxThreads(0),
31 	fWriterNest(0),
32 	fWriterThread(-1),
33 #endif
34 	fInit(B_NO_INIT)
35 {
36 #if !DEBUG
37 	rw_lock_init_etc(&fLock, baseName != NULL ? baseName : "some MultiLocker",
38 		baseName != NULL ? RW_LOCK_FLAG_CLONE_NAME : 0);
39 	fInit = B_OK;
40 #else
41 	// we are in debug mode!
42 	fLock = create_sem(LARGE_NUMBER, baseName != NULL ? baseName : "MultiLocker");
43 	if (fLock >= 0)
44 		fInit = B_OK;
45 
46 	// create the reader tracking list
47 	// the array needs to be large enough to hold all possible threads
48 	system_info sys;
49 	get_system_info(&sys);
50 	fMaxThreads = sys.max_threads;
51 	fDebugArray = (int32 *) malloc(fMaxThreads * sizeof(int32));
52 	for (int32 i = 0; i < fMaxThreads; i++) {
53 		fDebugArray[i] = 0;
54 	}
55 #endif
56 #if TIMING
57 	//initialize the counter variables
58 	rl_count = ru_count = wl_count = wu_count = islock_count = 0;
59 	rl_time = ru_time = wl_time = wu_time = islock_time = 0;
60 	#if DEBUG
61 		reg_count = unreg_count = 0;
62 		reg_time = unreg_time = 0;
63 	#endif
64 #endif
65 }
66 
67 
68 MultiLocker::~MultiLocker()
69 {
70 	// become the writer
71 	if (!IsWriteLocked())
72 		WriteLock();
73 
74 	// set locker to be uninitialized
75 	fInit = B_NO_INIT;
76 
77 #if !DEBUG
78 	rw_lock_destroy(&fLock);
79 #else
80 	delete_sem(fLock);
81 	free(fDebugArray);
82 #endif
83 #if TIMING
84 	// let's produce some performance numbers
85 	printf("MultiLocker Statistics:\n"
86 		"Avg ReadLock: %lld\n"
87 		"Avg ReadUnlock: %lld\n"
88 		"Avg WriteLock: %lld\n"
89 		"Avg WriteUnlock: %lld\n"
90 		"Avg IsWriteLocked: %lld\n",
91 		rl_count > 0 ? rl_time / rl_count : 0,
92 		ru_count > 0 ? ru_time / ru_count : 0,
93 		wl_count > 0 ? wl_time / wl_count : 0,
94 		wu_count > 0 ? wu_time / wu_count : 0,
95 		islock_count > 0 ? islock_time / islock_count : 0);
96 #endif
97 }
98 
99 
100 status_t
101 MultiLocker::InitCheck()
102 {
103 	return fInit;
104 }
105 
106 
107 #if !DEBUG
108 //	#pragma mark - Standard versions
109 
110 
111 bool
112 MultiLocker::IsWriteLocked() const
113 {
114 #if TIMING
115 	bigtime_t start = system_time();
116 #endif
117 
118 	bool writeLockHolder = false;
119 
120 	if (fInit == B_OK)
121 		writeLockHolder = (find_thread(NULL) == fLock.holder);
122 
123 #if TIMING
124 	bigtime_t end = system_time();
125 	islock_time += (end - start);
126 	islock_count++;
127 #endif
128 
129 	return writeLockHolder;
130 }
131 
132 
133 bool
134 MultiLocker::ReadLock()
135 {
136 #if TIMING
137 	bigtime_t start = system_time();
138 #endif
139 
140 	bool locked = (rw_lock_read_lock(&fLock) == B_OK);
141 
142 #if TIMING
143 	bigtime_t end = system_time();
144 	rl_time += (end - start);
145 	rl_count++;
146 #endif
147 
148 	return locked;
149 }
150 
151 
152 bool
153 MultiLocker::WriteLock()
154 {
155 #if TIMING
156 	bigtime_t start = system_time();
157 #endif
158 
159 	bool locked = (rw_lock_write_lock(&fLock) == B_OK);
160 
161 #if TIMING
162 	bigtime_t end = system_time();
163 	wl_time += (end - start);
164 	wl_count++;
165 #endif
166 
167 	return locked;
168 }
169 
170 
171 bool
172 MultiLocker::ReadUnlock()
173 {
174 #if TIMING
175 	bigtime_t start = system_time();
176 #endif
177 
178 	bool unlocked = (rw_lock_read_unlock(&fLock) == B_OK);
179 
180 #if TIMING
181 	bigtime_t end = system_time();
182 	ru_time += (end - start);
183 	ru_count++;
184 #endif
185 
186 	return unlocked;
187 }
188 
189 
190 bool
191 MultiLocker::WriteUnlock()
192 {
193 #if TIMING
194 	bigtime_t start = system_time();
195 #endif
196 
197 	bool unlocked = (rw_lock_write_unlock(&fLock) == B_OK);
198 
199 #if TIMING
200 	bigtime_t end = system_time();
201 	wu_time += (end - start);
202 	wu_count++;
203 #endif
204 
205 	return unlocked;
206 }
207 
208 
209 #else	// DEBUG
210 //	#pragma mark - Debug versions
211 
212 
213 bool
214 MultiLocker::IsWriteLocked() const
215 {
216 #if TIMING
217 	bigtime_t start = system_time();
218 #endif
219 
220 	bool writeLockHolder = false;
221 
222 	if (fInit == B_OK)
223 		writeLockHolder = (find_thread(NULL) == fWriterThread);
224 
225 #if TIMING
226 	bigtime_t end = system_time();
227 	islock_time += (end - start);
228 	islock_count++;
229 #endif
230 
231 	return writeLockHolder;
232 }
233 
234 
235 bool
236 MultiLocker::ReadLock()
237 {
238 	bool locked = false;
239 
240 	if (fInit != B_OK)
241 		debugger("lock not initialized");
242 
243 	if (IsWriteLocked()) {
244 		if (fWriterNest < 0)
245 			debugger("ReadLock() - negative writer nest count");
246 
247 		fWriterNest++;
248 		locked = true;
249 	} else {
250 		status_t status;
251 		do {
252 			status = acquire_sem(fLock);
253 		} while (status == B_INTERRUPTED);
254 
255 		locked = status == B_OK;
256 
257 		if (locked)
258 			_RegisterThread();
259 	}
260 
261 	return locked;
262 }
263 
264 
265 bool
266 MultiLocker::WriteLock()
267 {
268 	bool locked = false;
269 
270 	if (fInit != B_OK)
271 		debugger("lock not initialized");
272 
273 	if (IsWriteLocked()) {
274 		if (fWriterNest < 0)
275 			debugger("WriteLock() - negative writer nest count");
276 
277 		fWriterNest++;
278 		locked = true;
279 	} else {
280 		// new writer acquiring the lock
281 		if (IsReadLocked())
282 			debugger("Reader wants to become writer!");
283 
284 		status_t status;
285 		do {
286 			status = acquire_sem_etc(fLock, LARGE_NUMBER, 0, 0);
287 		} while (status == B_INTERRUPTED);
288 
289 		locked = status == B_OK;
290 		if (locked) {
291 			// record thread information
292 			fWriterThread = find_thread(NULL);
293 		}
294 	}
295 
296 	return locked;
297 }
298 
299 
300 bool
301 MultiLocker::ReadUnlock()
302 {
303 	bool unlocked = false;
304 
305 	if (IsWriteLocked()) {
306 		// writers simply decrement the nesting count
307 		fWriterNest--;
308 		if (fWriterNest < 0)
309 			debugger("ReadUnlock() - negative writer nest count");
310 
311 		unlocked = true;
312 	} else {
313 		// decrement and retrieve the read counter
314 		unlocked = release_sem_etc(fLock, 1, B_DO_NOT_RESCHEDULE) == B_OK;
315 		if (unlocked)
316 			_UnregisterThread();
317 	}
318 
319 	return unlocked;
320 }
321 
322 
323 bool
324 MultiLocker::WriteUnlock()
325 {
326 	bool unlocked = false;
327 
328 	if (IsWriteLocked()) {
329 		// if this is a nested lock simply decrement the nest count
330 		if (fWriterNest > 0) {
331 			fWriterNest--;
332 			unlocked = true;
333 		} else {
334 			// clear the information while still holding the lock
335 			fWriterThread = -1;
336 			unlocked = release_sem_etc(fLock, LARGE_NUMBER,
337 				B_DO_NOT_RESCHEDULE) == B_OK;
338 		}
339 	} else {
340 		char message[256];
341 		snprintf(message, sizeof(message), "Non-writer attempting to "
342 			"WriteUnlock() - write holder: %" B_PRId32, fWriterThread);
343 		debugger(message);
344 	}
345 
346 	return unlocked;
347 }
348 
349 
350 bool
351 MultiLocker::IsReadLocked() const
352 {
353 	if (fInit == B_NO_INIT)
354 		return false;
355 
356 	// determine if the lock is actually held
357 	thread_id thread = find_thread(NULL);
358 	return fDebugArray[thread % fMaxThreads] > 0;
359 }
360 
361 
362 /* these two functions manage the debug array for readers */
363 /* an array is created in the constructor large enough to hold */
364 /* an int32 for each of the maximum number of threads the system */
365 /* can have at one time. */
366 /* this array does not need to be locked because each running thread */
367 /* can be uniquely mapped to a slot in the array by performing: */
368 /* 		thread_id % max_threads */
369 /* each time ReadLock is called while in debug mode the thread_id	*/
370 /* is retrived in register_thread() and the count is adjusted in the */
371 /* array.  If register thread is ever called and the count is not 0 then */
372 /* an illegal, potentially deadlocking nested ReadLock occured */
373 /* unregister_thread clears the appropriate slot in the array */
374 
375 /* this system could be expanded or retracted to include multiple arrays of information */
376 /* in all fairness for it's current use, fDebugArray could be an array of bools */
377 
378 /* The disadvantage of this system for maintaining state is that it sucks up a ton of */
379 /* memory.  The other method (which would be slower), would involve an additional lock and */
380 /* traversing a list of cached information.  As this is only for a debug mode, the extra memory */
381 /* was not deemed to be a problem */
382 
383 void
384 MultiLocker::_RegisterThread()
385 {
386 	thread_id thread = find_thread(NULL);
387 
388 	if (fDebugArray[thread % fMaxThreads] != 0)
389 		debugger("Nested ReadLock!");
390 
391 	fDebugArray[thread % fMaxThreads]++;
392 }
393 
394 
395 void
396 MultiLocker::_UnregisterThread()
397 {
398 	thread_id thread = find_thread(NULL);
399 
400 	ASSERT(fDebugArray[thread % fMaxThreads] == 1);
401 	fDebugArray[thread % fMaxThreads]--;
402 }
403 
404 #endif	// DEBUG
405