xref: /haiku/src/system/libroot/posix/pthread/pthread_rwlock.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
1 /*
2  * Copyright 2022, Haiku, Inc. All rights reserved.
3  * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 #include <pthread.h>
8 
9 #include <new>
10 
11 #include <Debug.h>
12 
13 #include <AutoLocker.h>
14 #include <syscalls.h>
15 #include <user_mutex_defs.h>
16 #include <user_thread.h>
17 #include <util/DoublyLinkedList.h>
18 
19 #include "pthread_private.h"
20 
21 #define MAX_READER_COUNT	1000000
22 
23 #define RWLOCK_FLAG_SHARED	0x01
24 
25 
26 struct Waiter : DoublyLinkedListLinkImpl<Waiter> {
27 	Waiter(bool writer)
28 		:
29 		userThread(get_user_thread()),
30 		thread(find_thread(NULL)),
31 		writer(writer),
32 		queued(false)
33 	{
34 	}
35 
36 	user_thread*	userThread;
37 	thread_id		thread;
38 	status_t		status;
39 	bool			writer;
40 	bool			queued;
41 };
42 
43 typedef DoublyLinkedList<Waiter> WaiterList;
44 
45 
46 struct SharedRWLock {
47 	uint32_t	flags;
48 	int32_t		owner;
49 	int32_t		sem;
50 
51 	status_t Init()
52 	{
53 		flags = RWLOCK_FLAG_SHARED;
54 		owner = -1;
55 		sem = create_sem(MAX_READER_COUNT, "pthread rwlock");
56 
57 		return sem >= 0 ? B_OK : EAGAIN;
58 	}
59 
60 	status_t Destroy()
61 	{
62 		if (sem < 0)
63 			return B_BAD_VALUE;
64 		return delete_sem(sem) == B_OK ? B_OK : B_BAD_VALUE;
65 	}
66 
67 	status_t ReadLock(uint32 flags, bigtime_t timeout)
68 	{
69 		return acquire_sem_etc(sem, 1, flags, timeout);
70 	}
71 
72 	status_t WriteLock(uint32 flags, bigtime_t timeout)
73 	{
74 		status_t error = acquire_sem_etc(sem, MAX_READER_COUNT,
75 			flags, timeout);
76 		if (error == B_OK)
77 			owner = find_thread(NULL);
78 		return error;
79 	}
80 
81 	status_t Unlock()
82 	{
83 		if (find_thread(NULL) == owner) {
84 			owner = -1;
85 			return release_sem_etc(sem, MAX_READER_COUNT, 0);
86 		} else
87 			return release_sem(sem);
88 	}
89 };
90 
91 
92 struct LocalRWLock {
93 	uint32_t	flags;
94 	int32_t		owner;
95 	int32_t		mutex;
96 	int32_t		unused;
97 	int32_t		reader_count;
98 	int32_t		writer_count;
99 		// Note, that reader_count and writer_count are not used the same way.
100 		// writer_count includes the write lock owner as well as waiting
101 		// writers. reader_count includes read lock owners only.
102 	WaiterList	waiters;
103 
104 	status_t Init()
105 	{
106 		flags = 0;
107 		owner = -1;
108 		mutex = 0;
109 		reader_count = 0;
110 		writer_count = 0;
111 		new(&waiters) WaiterList;
112 
113 		return B_OK;
114 	}
115 
116 	status_t Destroy()
117 	{
118 		Locker locker(this);
119 		if (reader_count > 0 || waiters.Head() != NULL || writer_count > 0)
120 			return EBUSY;
121 		return B_OK;
122 	}
123 
124 	bool StructureLock()
125 	{
126 		const int32 oldValue = atomic_test_and_set((int32*)&mutex, B_USER_MUTEX_LOCKED, 0);
127 		if (oldValue != 0) {
128 			status_t status;
129 			do {
130 				status = _kern_mutex_lock((int32*)&mutex, NULL, 0, 0);
131 			} while (status == B_INTERRUPTED);
132 
133 			if (status != B_OK)
134 				return false;
135 		}
136 		return true;
137 	}
138 
139 	void StructureUnlock()
140 	{
141 		// Exit critical region: unlock the mutex
142 		int32 status = atomic_and((int32*)&mutex,
143 			~(int32)B_USER_MUTEX_LOCKED);
144 		if ((status & B_USER_MUTEX_WAITING) != 0)
145 			_kern_mutex_unblock((int32*)&mutex, 0);
146 	}
147 
148 	status_t ReadLock(uint32 flags, bigtime_t timeout)
149 	{
150 		Locker locker(this);
151 
152 		if (writer_count == 0) {
153 			reader_count++;
154 			return B_OK;
155 		}
156 
157 		return _Wait(false, flags, timeout);
158 	}
159 
160 	status_t WriteLock(uint32 flags, bigtime_t timeout)
161 	{
162 		Locker locker(this);
163 
164 		if (reader_count == 0 && writer_count == 0) {
165 			writer_count++;
166 			owner = find_thread(NULL);
167 			return B_OK;
168 		}
169 
170 		return _Wait(true, flags, timeout);
171 	}
172 
173 	status_t Unlock()
174 	{
175 		Locker locker(this);
176 
177 		if (find_thread(NULL) == owner) {
178 			writer_count--;
179 			owner = -1;
180 		} else
181 			reader_count--;
182 
183 		_Unblock();
184 
185 		return B_OK;
186 	}
187 
188 private:
189 	status_t _Wait(bool writer, uint32 flags, bigtime_t timeout)
190 	{
191 		if (timeout == 0)
192 			return B_TIMED_OUT;
193 
194 		if (writer_count == 1 && owner == find_thread(NULL))
195 			return EDEADLK;
196 
197 		Waiter waiter(writer);
198 		waiters.Add(&waiter);
199 		waiter.queued = true;
200 		waiter.userThread->wait_status = 1;
201 
202 		if (writer)
203 			writer_count++;
204 
205 		StructureUnlock();
206 		status_t error = _kern_block_thread(flags, timeout);
207 		StructureLock();
208 
209 		if (!waiter.queued)
210 			return waiter.status;
211 
212 		// we're still queued, which means an error (timeout, interrupt)
213 		// occurred
214 		waiters.Remove(&waiter);
215 
216 		if (writer)
217 			writer_count--;
218 
219 		_Unblock();
220 
221 		return error;
222 	}
223 
224 	void _Unblock()
225 	{
226 		// Check whether there any waiting threads at all and whether anyone
227 		// has the write lock
228 		Waiter* waiter = waiters.Head();
229 		if (waiter == NULL || owner >= 0)
230 			return;
231 
232 		// writer at head of queue?
233 		if (waiter->writer) {
234 			if (reader_count == 0) {
235 				waiter->status = B_OK;
236 				waiter->queued = false;
237 				waiters.Remove(waiter);
238 				owner = waiter->thread;
239 
240 				if (waiter->userThread->wait_status > 0)
241 					_kern_unblock_thread(waiter->thread, B_OK);
242 			}
243 			return;
244 		}
245 
246 		// wake up one or more readers -- we unblock more than one reader at
247 		// a time to save trips to the kernel
248 		while (!waiters.IsEmpty() && !waiters.Head()->writer) {
249 			static const int kMaxReaderUnblockCount = 128;
250 			thread_id readers[kMaxReaderUnblockCount];
251 			int readerCount = 0;
252 
253 			while (readerCount < kMaxReaderUnblockCount
254 					&& (waiter = waiters.Head()) != NULL
255 					&& !waiter->writer) {
256 				waiter->status = B_OK;
257 				waiter->queued = false;
258 				waiters.Remove(waiter);
259 
260 				if (waiter->userThread->wait_status > 0) {
261 					readers[readerCount++] = waiter->thread;
262 					reader_count++;
263 				}
264 			}
265 
266 			if (readerCount > 0)
267 				_kern_unblock_threads(readers, readerCount, B_OK);
268 		}
269 	}
270 
271 
272 	struct Locking {
273 		inline bool Lock(LocalRWLock* lockable)
274 		{
275 			return lockable->StructureLock();
276 		}
277 
278 		inline void Unlock(LocalRWLock* lockable)
279 		{
280 			lockable->StructureUnlock();
281 		}
282 	};
283 	typedef AutoLocker<LocalRWLock, Locking> Locker;
284 };
285 
286 
287 static void inline
288 assert_dummy()
289 {
290 	STATIC_ASSERT(sizeof(pthread_rwlock_t) >= sizeof(SharedRWLock));
291 	STATIC_ASSERT(sizeof(pthread_rwlock_t) >= sizeof(LocalRWLock));
292 }
293 
294 
295 // #pragma mark - public lock functions
296 
297 
298 int
299 pthread_rwlock_init(pthread_rwlock_t* lock, const pthread_rwlockattr_t* _attr)
300 {
301 	pthread_rwlockattr* attr = _attr != NULL ? *_attr : NULL;
302 	bool shared = attr != NULL && (attr->flags & RWLOCK_FLAG_SHARED) != 0;
303 
304 	if (shared)
305 		return ((SharedRWLock*)lock)->Init();
306 	else
307 		return ((LocalRWLock*)lock)->Init();
308 }
309 
310 
311 int
312 pthread_rwlock_destroy(pthread_rwlock_t* lock)
313 {
314 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
315 		return ((SharedRWLock*)lock)->Destroy();
316 	else
317 		return ((LocalRWLock*)lock)->Destroy();
318 }
319 
320 
321 int
322 pthread_rwlock_rdlock(pthread_rwlock_t* lock)
323 {
324 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
325 		return ((SharedRWLock*)lock)->ReadLock(0, B_INFINITE_TIMEOUT);
326 	else
327 		return ((LocalRWLock*)lock)->ReadLock(0, B_INFINITE_TIMEOUT);
328 }
329 
330 
331 int
332 pthread_rwlock_tryrdlock(pthread_rwlock_t* lock)
333 {
334 	status_t error;
335 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
336 		error = ((SharedRWLock*)lock)->ReadLock(B_ABSOLUTE_REAL_TIME_TIMEOUT, 0);
337 	else
338 		error = ((LocalRWLock*)lock)->ReadLock(B_ABSOLUTE_REAL_TIME_TIMEOUT, 0);
339 
340 	return error == B_TIMED_OUT ? EBUSY : error;
341 }
342 
343 
344 int
345 pthread_rwlock_clockrdlock(pthread_rwlock_t* lock, clockid_t clock_id,
346             const struct timespec *abstime)
347 {
348 	bigtime_t timeout = abstime->tv_sec * 1000000LL
349 		+ abstime->tv_nsec / 1000LL;
350 	uint32 flags = 0;
351 	if (timeout >= 0) {
352 		switch (clock_id) {
353 			case CLOCK_REALTIME:
354 				flags = B_ABSOLUTE_REAL_TIME_TIMEOUT;
355 				break;
356 			case CLOCK_MONOTONIC:
357 				flags = B_ABSOLUTE_TIMEOUT;
358 				break;
359 			default:
360 				return EINVAL;
361 		}
362 	}
363 
364 	status_t error;
365 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
366 		error = ((SharedRWLock*)lock)->ReadLock(flags, timeout);
367 	else
368 		error = ((LocalRWLock*)lock)->ReadLock(flags, timeout);
369 
370 	return error == B_TIMED_OUT ? EBUSY : error;
371 }
372 
373 
374 int
375 pthread_rwlock_timedrdlock(pthread_rwlock_t* lock,
376 	const struct timespec *abstime)
377 {
378 	return pthread_rwlock_clockrdlock(lock, CLOCK_REALTIME, abstime);
379 }
380 
381 
382 int
383 pthread_rwlock_wrlock(pthread_rwlock_t* lock)
384 {
385 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
386 		return ((SharedRWLock*)lock)->WriteLock(0, B_INFINITE_TIMEOUT);
387 	else
388 		return ((LocalRWLock*)lock)->WriteLock(0, B_INFINITE_TIMEOUT);
389 }
390 
391 
392 int
393 pthread_rwlock_trywrlock(pthread_rwlock_t* lock)
394 {
395 	status_t error;
396 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
397 		error = ((SharedRWLock*)lock)->WriteLock(B_ABSOLUTE_REAL_TIME_TIMEOUT, 0);
398 	else
399 		error = ((LocalRWLock*)lock)->WriteLock(B_ABSOLUTE_REAL_TIME_TIMEOUT, 0);
400 
401 	return error == B_TIMED_OUT ? EBUSY : error;
402 }
403 
404 
405 int
406 pthread_rwlock_clockwrlock (pthread_rwlock_t* lock, clockid_t clock_id,
407 	const struct timespec *abstime)
408 {
409 	bigtime_t timeout = abstime->tv_sec * 1000000LL
410 		+ abstime->tv_nsec / 1000LL;
411 	uint32 flags = 0;
412 	if (timeout >= 0) {
413 		switch (clock_id) {
414 			case CLOCK_REALTIME:
415 				flags = B_ABSOLUTE_REAL_TIME_TIMEOUT;
416 				break;
417 			case CLOCK_MONOTONIC:
418 				flags = B_ABSOLUTE_TIMEOUT;
419 				break;
420 			default:
421 				return EINVAL;
422 		}
423 	}
424 
425 	status_t error;
426 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
427 		error = ((SharedRWLock*)lock)->WriteLock(flags, timeout);
428 	else
429 		error = ((LocalRWLock*)lock)->WriteLock(flags, timeout);
430 
431 	return error == B_TIMED_OUT ? EBUSY : error;
432 }
433 
434 
435 int
436 pthread_rwlock_timedwrlock(pthread_rwlock_t* lock,
437 	const struct timespec *abstime)
438 {
439 	return pthread_rwlock_clockwrlock(lock, CLOCK_REALTIME, abstime);
440 }
441 
442 
443 int
444 pthread_rwlock_unlock(pthread_rwlock_t* lock)
445 {
446 	if ((lock->flags & RWLOCK_FLAG_SHARED) != 0)
447 		return ((SharedRWLock*)lock)->Unlock();
448 	else
449 		return ((LocalRWLock*)lock)->Unlock();
450 }
451 
452 
453 // #pragma mark - public attribute functions
454 
455 
456 int
457 pthread_rwlockattr_init(pthread_rwlockattr_t* _attr)
458 {
459 	pthread_rwlockattr* attr = (pthread_rwlockattr*)malloc(
460 		sizeof(pthread_rwlockattr));
461 	if (attr == NULL)
462 		return B_NO_MEMORY;
463 
464 	attr->flags = 0;
465 	*_attr = attr;
466 
467 	return 0;
468 }
469 
470 
471 int
472 pthread_rwlockattr_destroy(pthread_rwlockattr_t* _attr)
473 {
474 	pthread_rwlockattr* attr = *_attr;
475 
476 	free(attr);
477 	return 0;
478 }
479 
480 
481 int
482 pthread_rwlockattr_getpshared(const pthread_rwlockattr_t* _attr, int* shared)
483 {
484 	pthread_rwlockattr* attr = *_attr;
485 
486 	*shared = (attr->flags & RWLOCK_FLAG_SHARED) != 0
487 		? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE;
488 	return 0;
489 }
490 
491 
492 int
493 pthread_rwlockattr_setpshared(pthread_rwlockattr_t* _attr, int shared)
494 {
495 	pthread_rwlockattr* attr = *_attr;
496 
497 	if (shared == PTHREAD_PROCESS_SHARED)
498 		attr->flags |= RWLOCK_FLAG_SHARED;
499 	else
500 		attr->flags &= ~RWLOCK_FLAG_SHARED;
501 
502 	return 0;
503 }
504 
505