xref: /haiku/src/build/libbe/support/Locker.cpp (revision 3cb015b1ee509d69c643506e8ff573808c86dcfc)
1 /*
2  * Copyright (c) 2001-2005, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Author:	Erik Jaesler <erik@cgsoftware.com>
6  */
7 
8 /**	Semaphore-type class for thread safety */
9 
10 
11 #include <OS.h>
12 #include <Locker.h>
13 #include <SupportDefs.h>
14 
15 
16 //
17 // Data Member Documentation:
18 //
19 // The "fBenaphoreCount" member is set to 1 if the BLocker style is
20 // semaphore.  If the style is benaphore, it is initialized to 0 and
21 // is incremented atomically when it is acquired, decremented when it
22 // is released.  By setting the benaphore count to 1 when the style is
23 // semaphore, the benaphore effectively becomes a semaphore.  I was able
24 // to determine this is what Be's implementation does by testing the
25 // result of the CountLockRequests() member.
26 //
27 // The "fSemaphoreID" member holds the sem_id returned from create_sem()
28 // when the BLocker is constructed.  It is used to acquire and release
29 // the lock regardless of the lock style (semaphore or benaphore).
30 //
31 // The "fLockOwner" member holds the thread_id of the thread which
32 // currently holds the lock.  If no thread holds the lock, it is set to
33 // B_ERROR.
34 //
35 // The "fRecursiveCount" member holds a count of the number of times the
36 // thread holding the lock has acquired the lock without a matching unlock.
37 // It is basically the number of times the thread must call Unlock() before
38 // the lock can be acquired by a different thread.
39 //
40 
41 
42 //
43 // Constructors:
44 //
45 // All constructors just pass their arguments to InitLocker().  Note that
46 // the default for "name" is "some BLocker" and "benaphore_style" is true.
47 //
48 
49 BLocker::BLocker()
50 {
51 	InitLocker(NULL, true);
52 }
53 
54 
55 BLocker::BLocker(const char *name)
56 {
57 	InitLocker(name, true);
58 }
59 
60 
61 BLocker::BLocker(bool benaphoreStyle)
62 {
63 	InitLocker(NULL, benaphoreStyle);
64 }
65 
66 
67 BLocker::BLocker(const char *name, bool benaphoreStyle)
68 {
69 	InitLocker(name, benaphoreStyle);
70 }
71 
72 
73 //
74 //	This constructor is not documented.  The final argument is ignored for
75 //	now.  In Be's headers, its called "for_IPC".  DO NOT USE THIS
76 //	CONSTRUCTOR!
77 //
78 BLocker::BLocker(const char *name, bool benaphoreStyle,
79 	bool)
80 {
81 	InitLocker(name, benaphoreStyle);
82 }
83 
84 
85 //
86 // The destructor just deletes the semaphore.  By deleting the semaphore,
87 // any threads waiting to acquire the BLocker will be unblocked.
88 //
89 BLocker::~BLocker()
90 {
91 	delete_sem(fSemaphoreID);
92 }
93 
94 
95 bool
96 BLocker::Lock(void)
97 {
98 	status_t result;
99     return AcquireLock(B_INFINITE_TIMEOUT, &result);
100 }
101 
102 
103 status_t
104 BLocker::LockWithTimeout(bigtime_t timeout)
105 {
106     status_t result;
107 
108 	AcquireLock(timeout, &result);
109     return result;
110 }
111 
112 
113 
114 void
115 BLocker::Unlock(void)
116 {
117 	// If the thread currently holds the lockdecrement
118 	if (IsLocked()) {
119 		// Decrement the number of outstanding locks this thread holds
120 		// on this BLocker.
121 		fRecursiveCount--;
122 
123 		// If the recursive count is now at 0, that means the BLocker has
124 		// been released by the thread.
125 		if (fRecursiveCount == 0) {
126 			// The BLocker is no longer owned by any thread.
127 			fLockOwner = B_ERROR;
128 
129     		// Decrement the benaphore count and store the undecremented
130     		// value in oldBenaphoreCount.
131 			int32 oldBenaphoreCount = atomic_add(&fBenaphoreCount, -1);
132 
133 			// If the oldBenaphoreCount is greater than 1, then there is
134 			// at lease one thread waiting for the lock in the case of a
135 			// benaphore.
136    		    if (oldBenaphoreCount > 1) {
137  				// Since there are threads waiting for the lock, it must
138  				// be released.  Note, the old benaphore count will always be
139  				// greater than 1 for a semaphore so the release is always done.
140     			release_sem(fSemaphoreID);
141 	    	}
142 	    }
143     }
144 }
145 
146 
147 thread_id
148 BLocker::LockingThread(void) const
149 {
150     return fLockOwner;
151 }
152 
153 
154 bool
155 BLocker::IsLocked(void) const
156 {
157 	// This member returns true if the calling thread holds the lock.
158 	// The easiest way to determine this is to compare the result of
159 	// find_thread() to the fLockOwner.
160     return find_thread(NULL) == fLockOwner;
161 }
162 
163 
164 int32
165 BLocker::CountLocks(void) const
166 {
167     return fRecursiveCount;
168 }
169 
170 
171 int32
172 BLocker::CountLockRequests(void) const
173 {
174     return fBenaphoreCount;
175 }
176 
177 
178 sem_id
179 BLocker::Sem(void) const
180 {
181     return fSemaphoreID;
182 }
183 
184 
185 void
186 BLocker::InitLocker(const char *name, bool benaphore)
187 {
188 	if (name == NULL)
189 		name = "some BLocker";
190 
191 	if (benaphore) {
192 		// Because this is a benaphore, initialize the benaphore count and
193 		// create the semaphore.  Because this is a benaphore, the semaphore
194 		// count starts at 0 (ie acquired).
195 		fBenaphoreCount = 0;
196 		fSemaphoreID = create_sem(0, name);
197 	} else {
198 		// Because this is a semaphore, initialize the benaphore count to -1
199 		// and create the semaphore.  Because this is semaphore style, the
200 		// semaphore count starts at 1 so that one thread can acquire it and
201 		// the next thread to acquire it will block.
202 		fBenaphoreCount = 1;
203 		fSemaphoreID = create_sem(1, name);
204 	}
205 
206 	// The lock is currently not acquired so there is no owner.
207 	fLockOwner = B_ERROR;
208 
209 	// The lock is currently not acquired so the recursive count is zero.
210 	fRecursiveCount = 0;
211 }
212 
213 
214 bool
215 BLocker::AcquireLock(bigtime_t timeout, status_t *error)
216 {
217 	// By default, return no error.
218 	status_t status = B_OK;
219 
220 	// Only try to acquire the lock if the thread doesn't already own it.
221 	if (!IsLocked()) {
222 		// Increment the benaphore count and test to see if it was already greater
223 		// than 0.  If it is greater than 0, then some thread already has the
224 		// benaphore or the style is a semaphore.  Either way, we need to acquire
225 		// the semaphore in this case.
226 		int32 oldBenaphoreCount = atomic_add(&fBenaphoreCount, 1);
227 		if (oldBenaphoreCount > 0) {
228 			do {
229 				status = acquire_sem_etc(fSemaphoreID, 1, B_RELATIVE_TIMEOUT,
230 					timeout);
231 			} while (status == B_INTERRUPTED);
232 
233 			// Note, if the lock here does time out, the benaphore count
234 			// is not decremented.  By doing this, the benaphore count will
235 			// never go back to zero.  This means that the locking essentially
236 			// changes to semaphore style if this was a benaphore.
237 			//
238 			// Doing the decrement of the benaphore count when the acquisition
239 			// fails is a risky thing to do.  If you decrement the counter at
240 			// the same time the thread which holds the benaphore does an
241 			// Unlock(), there is serious risk of a race condition.
242 			//
243 			// If the Unlock() sees a positive count and releases the semaphore
244 			// and then the timed out thread decrements the count to 0, there
245 			// is no one to take the semaphore.  The next two threads will be
246 			// able to acquire the benaphore at the same time!  The first will
247 			// increment the counter and acquire the lock.  The second will
248 			// acquire the semaphore and therefore the lock.  Not good.
249 			//
250 			// This has been discussed on the becodetalk mailing list and
251 			// Trey from Be had this to say:
252 			//
253 			// I looked at the LockWithTimeout() code, and it does not have
254 			// _this_ (ie the race condition) problem.  It circumvents it by
255 			// NOT doing the atomic_add(&count, -1) if the semaphore
256 			// acquisition fails.  This means that if a
257 			// BLocker::LockWithTimeout() times out, all other Lock*() attempts
258 			// turn into guaranteed semaphore grabs, _with_ the overhead of a
259 			// (now) useless atomic_add().
260 			//
261 			// Given Trey's comments, it looks like Be took the same approach
262 			// I did.  The output of CountLockRequests() of Be's implementation
263 			// confirms Trey's comments also.
264 			//
265 			// Finally some thoughts for the future with this code:
266 			//   - If 2^31 timeouts occur on a 32-bit machine (ie today),
267 			//     the benaphore count will wrap to a negative number.  This
268 			//     would have unknown consequences on the ability of the BLocker
269 			//     to continue to function.
270 			//
271 		}
272 	}
273 
274 	// If the lock has successfully been acquired.
275 	if (status == B_OK) {
276 		// Set the lock owner to this thread and increment the recursive count
277 		// by one.  The recursive count is incremented because one more Unlock()
278 		// is now required to release the lock (ie, 0 => 1, 1 => 2 etc).
279 		fLockOwner = find_thread(NULL);
280 		fRecursiveCount++;
281 	}
282 
283 	if (error != NULL)
284 		*error = status;
285 
286 	// Return true if the lock has been acquired.
287 	return (status == B_OK);
288 }
289