xref: /haiku/src/kits/shared/RWLockManager.cpp (revision 2222d0559df303a9846a2fad53741f8b20b14d7c)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <RWLockManager.h>
7 
8 #include <AutoLocker.h>
9 
10 #include <syscalls.h>
11 #include <user_thread.h>
12 
13 
14 RWLockable::RWLockable()
15 	:
16 	fOwner(-1),
17 	fOwnerCount(0),
18 	fReaderCount(0)
19 {
20 }
21 
22 
23 RWLockManager::RWLockManager()
24 	:
25 	fLock("r/w lock manager")
26 {
27 }
28 
29 
30 RWLockManager::~RWLockManager()
31 {
32 }
33 
34 
35 bool
36 RWLockManager::ReadLock(RWLockable* lockable)
37 {
38 	AutoLocker<RWLockManager> locker(this);
39 
40 	if (lockable->fWaiters.IsEmpty()) {
41 		lockable->fReaderCount++;
42 		return true;
43 	}
44 
45 	return _Wait(lockable, false, B_INFINITE_TIMEOUT) == B_OK;
46 }
47 
48 
49 bool
50 RWLockManager::TryReadLock(RWLockable* lockable)
51 {
52 	AutoLocker<RWLockManager> locker(this);
53 
54 	if (lockable->fWaiters.IsEmpty()) {
55 		lockable->fReaderCount++;
56 		return true;
57 	}
58 
59 	return false;
60 }
61 
62 
63 status_t
64 RWLockManager::ReadLockWithTimeout(RWLockable* lockable, bigtime_t timeout)
65 {
66 	AutoLocker<RWLockManager> locker(this);
67 
68 	if (lockable->fWaiters.IsEmpty()) {
69 		lockable->fReaderCount++;
70 		return B_OK;
71 	}
72 
73 	return _Wait(lockable, false, timeout);
74 }
75 
76 
77 void
78 RWLockManager::ReadUnlock(RWLockable* lockable)
79 {
80 	AutoLocker<RWLockManager> locker(this);
81 
82 	if (lockable->fReaderCount <= 0) {
83 		debugger("RWLockManager::ReadUnlock(): Not read-locked!");
84 		return;
85 	}
86 
87 	if (--lockable->fReaderCount == 0)
88 		_Unblock(lockable);
89 }
90 
91 
92 bool
93 RWLockManager::WriteLock(RWLockable* lockable)
94 {
95 	AutoLocker<RWLockManager> locker(this);
96 
97 	thread_id thread = find_thread(NULL);
98 
99 	if (lockable->fOwner == thread) {
100 		lockable->fOwnerCount++;
101 		return true;
102 	}
103 
104 	if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
105 		lockable->fOwnerCount = 1;
106 		lockable->fOwner = find_thread(NULL);
107 		return true;
108 	}
109 
110 	return _Wait(lockable, true, B_INFINITE_TIMEOUT) == B_OK;
111 }
112 
113 
114 bool
115 RWLockManager::TryWriteLock(RWLockable* lockable)
116 {
117 	AutoLocker<RWLockManager> locker(this);
118 
119 	thread_id thread = find_thread(NULL);
120 
121 	if (lockable->fOwner == thread) {
122 		lockable->fOwnerCount++;
123 		return true;
124 	}
125 
126 	if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
127 		lockable->fOwnerCount++;
128 		lockable->fOwner = thread;
129 		return true;
130 	}
131 
132 	return false;
133 }
134 
135 
136 status_t
137 RWLockManager::WriteLockWithTimeout(RWLockable* lockable, bigtime_t timeout)
138 {
139 	AutoLocker<RWLockManager> locker(this);
140 
141 	thread_id thread = find_thread(NULL);
142 
143 	if (lockable->fOwner == thread) {
144 		lockable->fOwnerCount++;
145 		return B_OK;
146 	}
147 
148 	if (lockable->fReaderCount == 0 && lockable->fWaiters.IsEmpty()) {
149 		lockable->fOwnerCount++;
150 		lockable->fOwner = thread;
151 		return B_OK;
152 	}
153 
154 	return _Wait(lockable, true, timeout);
155 }
156 
157 
158 void
159 RWLockManager::WriteUnlock(RWLockable* lockable)
160 {
161 	AutoLocker<RWLockManager> locker(this);
162 
163 	if (find_thread(NULL) != lockable->fOwner) {
164 		debugger("RWLockManager::WriteUnlock(): Not write-locked by calling "
165 			"thread!");
166 		return;
167 	}
168 
169 	if (--lockable->fOwnerCount == 0) {
170 		lockable->fOwner = -1;
171 		_Unblock(lockable);
172 	}
173 }
174 
175 
176 status_t
177 RWLockManager::_Wait(RWLockable* lockable, bool writer, bigtime_t timeout)
178 {
179 	if (timeout == 0)
180 		return B_TIMED_OUT;
181 
182 	// enqueue a waiter
183 	RWLockable::Waiter waiter(writer);
184 	lockable->fWaiters.Add(&waiter);
185 	waiter.queued = true;
186 	get_user_thread()->wait_status = 1;
187 
188 	// wait
189 	Unlock();
190 
191 	status_t error;
192 	do {
193 		error = _kern_block_thread(
194 			timeout >= 0 ? B_RELATIVE_TIMEOUT : 0, timeout);
195 			// TODO: When interrupted we should adjust the timeout, respectively
196 			// convert to an absolute timeout in the first place!
197 	} while (error == B_INTERRUPTED);
198 
199 	Lock();
200 
201 	if (!waiter.queued)
202 		return waiter.status;
203 
204 	// we're still queued, which means an error (timeout, interrupt)
205 	// occurred
206 	lockable->fWaiters.Remove(&waiter);
207 
208 	_Unblock(lockable);
209 
210 	return error;
211 }
212 
213 
214 void
215 RWLockManager::_Unblock(RWLockable* lockable)
216 {
217 	// Check whether there any waiting threads at all and whether anyone
218 	// has the write lock
219 	RWLockable::Waiter* waiter = lockable->fWaiters.Head();
220 	if (waiter == NULL || lockable->fOwner >= 0)
221 		return;
222 
223 	// writer at head of queue?
224 	if (waiter->writer) {
225 		if (lockable->fReaderCount == 0) {
226 			waiter->status = B_OK;
227 			waiter->queued = false;
228 			lockable->fWaiters.Remove(waiter);
229 			lockable->fOwner = waiter->thread;
230 			lockable->fOwnerCount = 1;
231 
232 			_kern_unblock_thread(waiter->thread, B_OK);
233 		}
234 		return;
235 	}
236 
237 	// wake up one or more readers -- we unblock more than one reader at
238 	// a time to save trips to the kernel
239 	while (!lockable->fWaiters.IsEmpty()
240 			&& !lockable->fWaiters.Head()->writer) {
241 		static const int kMaxReaderUnblockCount = 128;
242 		thread_id readers[kMaxReaderUnblockCount];
243 		int readerCount = 0;
244 
245 		while (readerCount < kMaxReaderUnblockCount
246 				&& (waiter = lockable->fWaiters.Head()) != NULL
247 				&& !waiter->writer) {
248 			waiter->status = B_OK;
249 			waiter->queued = false;
250 			lockable->fWaiters.Remove(waiter);
251 
252 			readers[readerCount++] = waiter->thread;
253 			lockable->fReaderCount++;
254 		}
255 
256 		if (readerCount > 0)
257 			_kern_unblock_threads(readers, readerCount, B_OK);
258 	}
259 }
260