1/*	$OpenBSD: rthread_rwlock_compat.c,v 1.2 2022/05/14 14:52:20 cheloha Exp $ */
2/*
3 * Copyright (c) 2004,2005 Ted Unangst <tedu@openbsd.org>
4 * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
5 * All Rights Reserved.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19/*
20 * rwlocks
21 */
22
23#include <assert.h>
24#include <stdlib.h>
25#include <unistd.h>
26#include <errno.h>
27
28#include <pthread.h>
29
30#include "rthread.h"
31
32static _atomic_lock_t rwlock_init_lock = _SPINLOCK_UNLOCKED;
33
34int
35pthread_rwlock_init(pthread_rwlock_t *lockp,
36    const pthread_rwlockattr_t *attrp __unused)
37{
38	pthread_rwlock_t lock;
39
40	lock = calloc(1, sizeof(*lock));
41	if (!lock)
42		return (errno);
43	lock->lock = _SPINLOCK_UNLOCKED;
44	TAILQ_INIT(&lock->writers);
45
46	*lockp = lock;
47
48	return (0);
49}
50DEF_STD(pthread_rwlock_init);
51
52int
53pthread_rwlock_destroy(pthread_rwlock_t *lockp)
54{
55	pthread_rwlock_t lock;
56
57	assert(lockp);
58	lock = *lockp;
59	if (lock) {
60		if (lock->readers || !TAILQ_EMPTY(&lock->writers)) {
61#define MSG "pthread_rwlock_destroy on rwlock with waiters!\n"
62			write(2, MSG, sizeof(MSG) - 1);
63#undef MSG
64			return (EBUSY);
65		}
66		free(lock);
67	}
68	*lockp = NULL;
69
70	return (0);
71}
72
73static int
74_rthread_rwlock_ensure_init(pthread_rwlock_t *lockp)
75{
76	int ret = 0;
77
78	/*
79	 * If the rwlock is statically initialized, perform the dynamic
80	 * initialization.
81	 */
82	if (*lockp == NULL)
83	{
84		_spinlock(&rwlock_init_lock);
85		if (*lockp == NULL)
86			ret = pthread_rwlock_init(lockp, NULL);
87		_spinunlock(&rwlock_init_lock);
88	}
89	return (ret);
90}
91
92
93static int
94_rthread_rwlock_rdlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
95    int try)
96{
97	pthread_rwlock_t lock;
98	pthread_t thread = pthread_self();
99	int error;
100
101	if ((error = _rthread_rwlock_ensure_init(lockp)))
102		return (error);
103
104	lock = *lockp;
105	_rthread_debug(5, "%p: rwlock_rdlock %p\n", (void *)thread,
106	    (void *)lock);
107	_spinlock(&lock->lock);
108
109	/* writers have precedence */
110	if (lock->owner == NULL && TAILQ_EMPTY(&lock->writers))
111		lock->readers++;
112	else if (try)
113		error = EBUSY;
114	else if (lock->owner == thread)
115		error = EDEADLK;
116	else {
117		do {
118			if (__thrsleep(lock, CLOCK_REALTIME, abstime,
119			    &lock->lock, NULL) == EWOULDBLOCK)
120				return (ETIMEDOUT);
121			_spinlock(&lock->lock);
122		} while (lock->owner != NULL || !TAILQ_EMPTY(&lock->writers));
123		lock->readers++;
124	}
125	_spinunlock(&lock->lock);
126
127	return (error);
128}
129
130int
131pthread_rwlock_rdlock(pthread_rwlock_t *lockp)
132{
133	return (_rthread_rwlock_rdlock(lockp, NULL, 0));
134}
135
136int
137pthread_rwlock_tryrdlock(pthread_rwlock_t *lockp)
138{
139	return (_rthread_rwlock_rdlock(lockp, NULL, 1));
140}
141
142int
143pthread_rwlock_timedrdlock(pthread_rwlock_t *lockp,
144    const struct timespec *abstime)
145{
146	if (abstime == NULL || !timespecisvalid(abstime))
147		return (EINVAL);
148	return (_rthread_rwlock_rdlock(lockp, abstime, 0));
149}
150
151
152static int
153_rthread_rwlock_wrlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
154    int try)
155{
156	pthread_rwlock_t lock;
157	pthread_t thread = pthread_self();
158	int error;
159
160	if ((error = _rthread_rwlock_ensure_init(lockp)))
161		return (error);
162
163	lock = *lockp;
164
165	_rthread_debug(5, "%p: rwlock_timedwrlock %p\n", (void *)thread,
166	    (void *)lock);
167	_spinlock(&lock->lock);
168	if (lock->readers == 0 && lock->owner == NULL)
169		lock->owner = thread;
170	else if (try)
171		error = EBUSY;
172	else if (lock->owner == thread)
173		error = EDEADLK;
174	else {
175		int do_wait;
176
177		/* gotta block */
178		TAILQ_INSERT_TAIL(&lock->writers, thread, waiting);
179		do {
180			do_wait = __thrsleep(thread, CLOCK_REALTIME, abstime,
181			    &lock->lock, NULL) != EWOULDBLOCK;
182			_spinlock(&lock->lock);
183		} while (lock->owner != thread && do_wait);
184
185		if (lock->owner != thread) {
186			/* timed out, sigh */
187			TAILQ_REMOVE(&lock->writers, thread, waiting);
188			error = ETIMEDOUT;
189		}
190	}
191	_spinunlock(&lock->lock);
192
193	return (error);
194}
195
196int
197pthread_rwlock_wrlock(pthread_rwlock_t *lockp)
198{
199	return (_rthread_rwlock_wrlock(lockp, NULL, 0));
200}
201
202int
203pthread_rwlock_trywrlock(pthread_rwlock_t *lockp)
204{
205	return (_rthread_rwlock_wrlock(lockp, NULL, 1));
206}
207
208int
209pthread_rwlock_timedwrlock(pthread_rwlock_t *lockp,
210    const struct timespec *abstime)
211{
212	if (abstime == NULL || !timespecisvalid(abstime))
213		return (EINVAL);
214	return (_rthread_rwlock_wrlock(lockp, abstime, 0));
215}
216
217
218int
219pthread_rwlock_unlock(pthread_rwlock_t *lockp)
220{
221	pthread_rwlock_t lock;
222	pthread_t thread = pthread_self();
223	pthread_t next;
224	int was_writer;
225
226	lock = *lockp;
227
228	_rthread_debug(5, "%p: rwlock_unlock %p\n", (void *)thread,
229	    (void *)lock);
230	_spinlock(&lock->lock);
231	if (lock->owner != NULL) {
232		assert(lock->owner == thread);
233		was_writer = 1;
234	} else {
235		assert(lock->readers > 0);
236		lock->readers--;
237		if (lock->readers > 0)
238			goto out;
239		was_writer = 0;
240	}
241
242	lock->owner = next = TAILQ_FIRST(&lock->writers);
243	if (next != NULL) {
244		/* dequeue and wake first writer */
245		TAILQ_REMOVE(&lock->writers, next, waiting);
246		_spinunlock(&lock->lock);
247		__thrwakeup(next, 1);
248		return (0);
249	}
250
251	/* could there have been blocked readers?  wake them all */
252	if (was_writer)
253		__thrwakeup(lock, 0);
254out:
255	_spinunlock(&lock->lock);
256
257	return (0);
258}
259