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
23const int32 LARGE_NUMBER = 100000;
24
25
26MultiLocker::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
68MultiLocker::~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
100status_t
101MultiLocker::InitCheck()
102{
103	return fInit;
104}
105
106
107#if !DEBUG
108//	#pragma mark - Standard versions
109
110
111bool
112MultiLocker::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
133bool
134MultiLocker::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
152bool
153MultiLocker::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
171bool
172MultiLocker::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
190bool
191MultiLocker::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
213bool
214MultiLocker::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
235bool
236MultiLocker::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
265bool
266MultiLocker::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
300bool
301MultiLocker::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
323bool
324MultiLocker::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
350bool
351MultiLocker::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
383void
384MultiLocker::_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
395void
396MultiLocker::_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