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