1 /*
2  * Copyright (c) 2006-2024 RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2010-10-26     Bernard      the first version
9  */
10 
11 #include <pthread.h>
12 
pthread_rwlockattr_init(pthread_rwlockattr_t * attr)13 int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
14 {
15     if (!attr)
16         return EINVAL;
17     *attr = PTHREAD_PROCESS_PRIVATE;
18 
19     return 0;
20 }
21 RTM_EXPORT(pthread_rwlockattr_init);
22 
pthread_rwlockattr_destroy(pthread_rwlockattr_t * attr)23 int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
24 {
25     if (!attr)
26         return EINVAL;
27 
28     return 0;
29 }
30 RTM_EXPORT(pthread_rwlockattr_destroy);
31 
pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * attr,int * pshared)32 int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr,
33                                   int                        *pshared)
34 {
35     if (!attr || !pshared)
36         return EINVAL;
37 
38     *pshared = PTHREAD_PROCESS_PRIVATE;
39 
40     return 0;
41 }
42 RTM_EXPORT(pthread_rwlockattr_getpshared);
43 
pthread_rwlockattr_setpshared(pthread_rwlockattr_t * attr,int pshared)44 int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared)
45 {
46     if (!attr || pshared != PTHREAD_PROCESS_PRIVATE)
47         return EINVAL;
48 
49     return 0;
50 }
51 RTM_EXPORT(pthread_rwlockattr_setpshared);
52 
53 /**
54  * @brief Initializes a read-write lock.
55  *
56  * This function initializes the read-write lock object pointed to by `rwlock` with the
57  * attributes specified by `attr`. If `attr` is `NULL`, the default attributes are used.
58  * A read-write lock allows multiple threads to read or a single thread to write, but not both simultaneously.
59  *
60  * @param rwlock A pointer to the read-write lock object to be initialized.
61  *               Must point to valid memory.
62  * @param attr A pointer to the attributes for the read-write lock.
63  *             If `NULL`, default attributes are applied.
64  *
65  * @return
66  * - `0` on success.
67  * - A non-zero error code on failure, including:
68  *   - `EINVAL`: Invalid attributes.
69  *
70  * @note
71  * - The read-write lock must be destroyed using `pthread_rwlock_destroy()` when it is no longer needed.
72  * - 'rw_mutex' is used for protecting rwlock data.
73  *   'rw_condreaders' is a condition variable for controlling readers.
74  *   'rw_condwriters' is a condition variable for controlling writers.
75  *
76  * @see pthread_rwlock_destroy, pthread_rwlock_rdlock, pthread_rwlock_wrlock, pthread_rwlock_unlock
77  */
pthread_rwlock_init(pthread_rwlock_t * rwlock,const pthread_rwlockattr_t * attr)78 int pthread_rwlock_init(pthread_rwlock_t           *rwlock,
79                         const pthread_rwlockattr_t *attr)
80 {
81     if (!rwlock)
82         return EINVAL;
83 
84     rwlock->attr = PTHREAD_PROCESS_PRIVATE;
85     pthread_mutex_init(&(rwlock->rw_mutex), NULL);
86     pthread_cond_init(&(rwlock->rw_condreaders), NULL);
87     pthread_cond_init(&(rwlock->rw_condwriters), NULL);
88 
89     rwlock->rw_nwaitwriters = 0;
90     rwlock->rw_nwaitreaders = 0;
91     rwlock->rw_refcount = 0;
92 
93     return 0;
94 }
95 RTM_EXPORT(pthread_rwlock_init);
96 
97 /**
98  * @brief Destroys a read-write lock.
99  *
100  * This function destroys the read-write lock object pointed to by `rwlock`. After
101  * the lock is destroyed, it cannot be used until it is reinitialized with
102  * `pthread_rwlock_init`. Any threads currently blocked on the lock are affected by the destruction.
103  *
104  * @param rwlock A pointer to the read-write lock object to be destroyed.
105  *               Must point to a valid, initialized read-write lock.
106  *
107  * @return
108  * - `0` on success.
109  * - A non-zero error code on failure, including:
110  *   - `EINVAL`: The `rwlock` is invalid or uninitialized.
111  *   - `EBUSY`: The lock is currently in use by a thread, and cannot be destroyed.
112  *
113  * @note
114  * - The read-write lock must not be in use (i.e., no threads should be blocked on it)
115  *   when `pthread_rwlock_destroy` is called.
116  * - Calling this function on an uninitialized or destroyed lock will result in undefined behavior.
117  * - Ensure that all threads have unlocked the lock before attempting to destroy it.
118  *
119  * @see pthread_rwlock_init, pthread_rwlock_rdlock, pthread_rwlock_wrlock, pthread_rwlock_unlock
120  */
pthread_rwlock_destroy(pthread_rwlock_t * rwlock)121 int pthread_rwlock_destroy (pthread_rwlock_t *rwlock)
122 {
123     int result;
124 
125     if (!rwlock)
126         return EINVAL;
127     if (rwlock->attr == -1)
128         return 0; /* rwlock is not initialized */
129 
130     if ( (result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
131         return(result);
132 
133     if (rwlock->rw_refcount != 0 ||
134         rwlock->rw_nwaitreaders != 0 ||
135         rwlock->rw_nwaitwriters != 0)
136     {
137         result = EBUSY;
138 
139         return result;
140     }
141     else
142     {
143         /* check whether busy */
144         result = rt_sem_trytake(&(rwlock->rw_condreaders.sem));
145         if (result == RT_EOK)
146         {
147             result = rt_sem_trytake(&(rwlock->rw_condwriters.sem));
148             if (result == RT_EOK)
149             {
150                 rt_sem_release(&(rwlock->rw_condreaders.sem));
151                 rt_sem_release(&(rwlock->rw_condwriters.sem));
152 
153                 pthread_cond_destroy(&rwlock->rw_condreaders);
154                 pthread_cond_destroy(&rwlock->rw_condwriters);
155             }
156             else
157             {
158                 rt_sem_release(&(rwlock->rw_condreaders.sem));
159                 result = EBUSY;
160             }
161         }
162         else
163             result = EBUSY;
164     }
165 
166     pthread_mutex_unlock(&rwlock->rw_mutex);
167     if (result == 0)
168         pthread_mutex_destroy(&rwlock->rw_mutex);
169 
170     return result;
171 }
172 RTM_EXPORT(pthread_rwlock_destroy);
173 
174 /**
175  * @brief Acquire a read lock on a read-write lock.
176  *
177  * This function locks the specified read-write lock for reading. If the lock
178  * is already held by one or more threads for reading, the calling thread
179  * can acquire the lock as well (shared access). However, if the lock is
180  * held by another writer thread, or other writer thread has been waiting
181  * for the lock, the calling thread will block until the write lock is released.
182  *
183  * @param rwlock A pointer to the read-write lock to be locked.
184  *
185  * @return - 0 on success.
186  *         - EINVAL if the rwlock is invalid.
187  *         - EDEADLK if a deadlock condition is detected (optional; implementation-dependent).
188  *
189  * @note A thread that has acquired a read lock must eventually release it
190  *       using `pthread_rwlock_unlock`. Multiple read locks can be held
191  *       simultaneously, but a write lock excludes all other locks.
192  *
193  * @see pthread_rwlock_unlock
194  * @see pthread_rwlock_wrlock
195  * @see pthread_rwlock_tryrdlock
196  */
pthread_rwlock_rdlock(pthread_rwlock_t * rwlock)197 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
198 {
199     int result;
200 
201     if (!rwlock)
202         return EINVAL;
203     if (rwlock->attr == -1)
204         pthread_rwlock_init(rwlock, NULL);
205 
206     if ((result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
207         return(result);
208 
209     /* give preference to waiting writers */
210     while (rwlock->rw_refcount < 0 || rwlock->rw_nwaitwriters > 0)
211     {
212         rwlock->rw_nwaitreaders++;
213         /* rw_mutex will be released when waiting for rw_condreaders */
214         result = pthread_cond_wait(&rwlock->rw_condreaders, &rwlock->rw_mutex);
215         /* rw_mutex should have been taken again when returned from waiting */
216         rwlock->rw_nwaitreaders--;
217         if (result != 0) /* wait error */
218             break;
219     }
220 
221     /* another reader has a read lock */
222     if (result == 0)
223         rwlock->rw_refcount++;
224 
225     pthread_mutex_unlock(&rwlock->rw_mutex);
226 
227     return (result);
228 }
229 RTM_EXPORT(pthread_rwlock_rdlock);
230 
231 /**
232  * @brief Try to acquire a read lock on a read-write lock without blocking.
233  *
234  * This function attempts to acquire a read lock on the specified read-write lock.
235  * If the lock is already held for writing, the function will return immediately
236  * without blocking the calling thread. If the lock is available for reading,
237  * it will be acquired successfully.
238  *
239  * @param rwlock A pointer to the read-write lock to attempt to lock.
240  *
241  * @return - 0 on success, indicating the read lock was acquired.
242  *         - EBUSY if the lock is currently held for writing by another thread.
243  *         - EINVAL if the rwlock is invalid.
244  *
245  * @note This function is non-blocking and returns immediately if the lock
246  *       cannot be acquired. After successfully acquiring the read lock,
247  *       the thread must release it using `pthread_rwlock_unlock`.
248  *
249  * @see pthread_rwlock_unlock
250  * @see pthread_rwlock_rdlock
251  * @see pthread_rwlock_trywrlock
252  */
pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlock)253 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
254 {
255     int result;
256 
257     if (!rwlock)
258         return EINVAL;
259     if (rwlock->attr == -1)
260         pthread_rwlock_init(rwlock, NULL);
261 
262     if ((result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
263         return(result);
264 
265     if (rwlock->rw_refcount < 0 || rwlock->rw_nwaitwriters > 0)
266         result = EBUSY;                 /* held by a writer or waiting writers */
267     else
268         rwlock->rw_refcount++;          /* increment count of reader locks */
269 
270     pthread_mutex_unlock(&rwlock->rw_mutex);
271 
272     return(result);
273 }
274 RTM_EXPORT(pthread_rwlock_tryrdlock);
275 
276 /**
277  * @brief Acquire a read lock on a read-write lock with a timeout.
278  *
279  * This function attempts to lock the specified read-write lock for reading,
280  * blocking until the lock becomes available or the specified timeout expires.
281  * If the lock is held for writing by another thread, the calling thread will
282  * block, but only up to the time specified by `abstime`.
283  *
284  * @param rwlock A pointer to the read-write lock to be locked.
285  * @param abstime A pointer to a `timespec` structure specifying the
286  *                    absolute timeout (in seconds and nanoseconds since the
287  *                    Epoch, 1970-01-01 00:00:00 UTC).
288  *
289  * @return - 0 on success, indicating the read lock was acquired.
290  *         - ETIMEDOUT if the timeout expired before the lock could be acquired.
291  *         - EINVAL if the `rwlock` is invalid or `abstime` contains invalid values.
292  *         - EDEADLK if a deadlock condition is detected (optional; implementation-dependent).
293  *
294  * @note The timeout is specified as an absolute time (not relative). After
295  *       acquiring the read lock, the thread must release it using
296  *       `pthread_rwlock_unlock`.
297  *
298  * @warning If the system clock is changed (e.g., via manual adjustment or
299  *          NTP synchronization), the timeout behavior may be affected.
300  *
301  * @see pthread_rwlock_unlock
302  * @see pthread_rwlock_rdlock
303  * @see pthread_rwlock_tryrdlock
304  */
pthread_rwlock_timedrdlock(pthread_rwlock_t * rwlock,const struct timespec * abstime)305 int pthread_rwlock_timedrdlock(pthread_rwlock_t      *rwlock,
306                                const struct timespec *abstime)
307 {
308     int result;
309 
310     if (!rwlock)
311         return EINVAL;
312     if (rwlock->attr == -1)
313         pthread_rwlock_init(rwlock, NULL);
314 
315     if ( (result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
316         return(result);
317 
318     /* give preference to waiting writers */
319     while (rwlock->rw_refcount < 0 || rwlock->rw_nwaitwriters > 0)
320     {
321         rwlock->rw_nwaitreaders++;
322         /* rw_mutex will be released when waiting for rw_condreaders */
323         result = pthread_cond_timedwait(&rwlock->rw_condreaders, &rwlock->rw_mutex, abstime);
324         /* rw_mutex should have been taken again when returned from waiting */
325         rwlock->rw_nwaitreaders--;
326         if (result != 0)
327             break;
328     }
329 
330     /* another reader has a read lock */
331     if (result == 0)
332         rwlock->rw_refcount++;
333 
334     pthread_mutex_unlock(&rwlock->rw_mutex);
335 
336     return (result);
337 }
338 RTM_EXPORT(pthread_rwlock_timedrdlock);
339 
340 /**
341  * @brief Acquire a write lock on a read-write lock with a timeout.
342  *
343  * This function attempts to acquire a write lock on the specified read-write lock,
344  * blocking until the lock becomes available or the specified timeout expires.
345  * If the lock is already held for reading or writing by another thread, the
346  * calling thread will block, but only up to the time specified by `abstime`.
347  *
348  * @param rwlock A pointer to the read-write lock to be locked.
349  * @param abstime A pointer to a `timespec` structure specifying the
350  *                    absolute timeout (in seconds and nanoseconds since the
351  *                    Epoch, 1970-01-01 00:00:00 UTC).
352  *
353  * @return
354  * - 0 on success, indicating the write lock was acquired.
355  * - EINVAL if the `rwlock` is invalid or `abstime` contains invalid values.
356  * - EDEADLK if a deadlock condition is detected (optional; implementation-dependent).
357  *
358  * @note The timeout is specified as an absolute time (not relative). After
359  *       acquiring the write lock, the thread must release it using
360  *       `pthread_rwlock_unlock`.
361  *
362  * @warning If the system clock is adjusted (e.g., manually or via NTP synchronization),
363  *          the timeout behavior may be affected.
364  *
365  * @see pthread_rwlock_unlock
366  * @see pthread_rwlock_wrlock
367  * @see pthread_rwlock_trywrlock
368  */
pthread_rwlock_timedwrlock(pthread_rwlock_t * rwlock,const struct timespec * abstime)369 int pthread_rwlock_timedwrlock(pthread_rwlock_t      *rwlock,
370                                const struct timespec *abstime)
371 {
372     int result;
373 
374     if (!rwlock)
375         return EINVAL;
376     if (rwlock->attr == -1)
377         pthread_rwlock_init(rwlock, NULL);
378 
379     if ((result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
380         return(result);
381 
382     while (rwlock->rw_refcount != 0)
383     {
384         rwlock->rw_nwaitwriters++;
385         /* rw_mutex will be released when waiting for rw_condwriters */
386         result = pthread_cond_timedwait(&rwlock->rw_condwriters, &rwlock->rw_mutex, abstime);
387         /* rw_mutex should have been taken again when returned from waiting */
388         rwlock->rw_nwaitwriters--;
389 
390         if (result != 0)
391             break;
392     }
393 
394     if (result == 0)
395         rwlock->rw_refcount = -1;
396 
397     pthread_mutex_unlock(&rwlock->rw_mutex);
398 
399     return(result);
400 }
401 RTM_EXPORT(pthread_rwlock_timedwrlock);
402 
403 /**
404  * @brief Try to acquire a write lock on a read-write lock without blocking.
405  *
406  * This function attempts to acquire a write lock on the specified read-write lock.
407  * If the lock is already held for reading or writing by another thread, the function
408  * will return immediately without blocking the calling thread. If the lock is
409  * available, it will be acquired successfully.
410  *
411  * @param rwlock A pointer to the read-write lock to attempt to lock.
412  *
413  * @return
414  * - 0 on success, indicating the write lock was acquired.
415  * - EBUSY if the lock is currently held by another thread (read or write lock).
416  * - EINVAL if the `rwlock` is invalid.
417  *
418  * @note This function is non-blocking and returns immediately if the lock cannot
419  *       be acquired. After successfully acquiring the write lock, the thread must
420  *       release it using `pthread_rwlock_unlock`.
421  *
422  * @see pthread_rwlock_unlock
423  * @see pthread_rwlock_wrlock
424  * @see pthread_rwlock_timedwrlock
425  */
pthread_rwlock_trywrlock(pthread_rwlock_t * rwlock)426 int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
427 {
428     int result;
429 
430     if (!rwlock)
431         return EINVAL;
432     if (rwlock->attr == -1)
433         pthread_rwlock_init(rwlock, NULL);
434 
435     if ((result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
436         return(result);
437 
438     if (rwlock->rw_refcount != 0)
439         result = EBUSY;                 /* held by either writer or reader(s) */
440     else
441         rwlock->rw_refcount = -1;       /* available, indicate a writer has it */
442 
443     pthread_mutex_unlock(&rwlock->rw_mutex);
444 
445     return(result);
446 }
447 RTM_EXPORT(pthread_rwlock_trywrlock);
448 
449 /**
450  * @brief Release a read or write lock on a read-write lock.
451  *
452  * This function unlocks the specified read-write lock, releasing either a read
453  * lock or a write lock held by the calling thread. If the calling thread does
454  * not hold the lock, the behavior is undefined.
455  *
456  * @param rwlock A pointer to the read-write lock to be unlocked.
457  *
458  * @return
459  * - 0 on success.
460  * - EINVAL if the `rwlock` is invalid.
461  *
462  * @note
463  * - This function must only be called by the thread that successfully acquired
464  *   the lock.
465  *
466  * @see pthread_rwlock_rdlock
467  * @see pthread_rwlock_wrlock
468  * @see pthread_rwlock_tryrdlock
469  * @see pthread_rwlock_trywrlock
470  */
pthread_rwlock_unlock(pthread_rwlock_t * rwlock)471 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
472 {
473     int result;
474 
475     if (!rwlock)
476         return EINVAL;
477     if (rwlock->attr == -1)
478         pthread_rwlock_init(rwlock, NULL);
479 
480     if ( (result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
481         return(result);
482 
483     if (rwlock->rw_refcount > 0)
484         rwlock->rw_refcount--;              /* releasing a reader */
485     else if (rwlock->rw_refcount == -1)
486         rwlock->rw_refcount = 0;            /* releasing a writer */
487 
488     /* give preference to waiting writers over waiting readers */
489     if (rwlock->rw_nwaitwriters > 0)
490     {
491         if (rwlock->rw_refcount == 0)
492             result = pthread_cond_signal(&rwlock->rw_condwriters);
493     }
494     else if (rwlock->rw_nwaitreaders > 0)
495     {
496         result = pthread_cond_broadcast(&rwlock->rw_condreaders);
497     }
498 
499     pthread_mutex_unlock(&rwlock->rw_mutex);
500 
501     return(result);
502 }
503 RTM_EXPORT(pthread_rwlock_unlock);
504 
505 /**
506  * @brief Acquire a write lock on a read-write lock.
507  *
508  * This function locks the specified read-write lock for writing. If the lock
509  * is already held by another thread for reading or writing, the calling thread
510  * blocks until the lock becomes available.
511  *
512  * @param rwlock A pointer to the read-write lock to be locked.
513  *
514  * @return
515  * - 0 on success, indicating the write lock was acquired.
516  * - EINVAL if the `rwlock` is invalid.
517  * - EDEADLK if a deadlock condition is detected (optional; implementation-dependent).
518  *
519  * @note
520  * - A write lock is exclusive, meaning no other thread can acquire a read or
521  *   write lock while a write lock is held.
522  * - The thread that successfully acquires the write lock must release it using
523  *   `pthread_rwlock_unlock`.
524  *
525  * @see pthread_rwlock_unlock
526  * @see pthread_rwlock_trywrlock
527  * @see pthread_rwlock_timedwrlock
528  * @see pthread_rwlock_rdlock
529  */
pthread_rwlock_wrlock(pthread_rwlock_t * rwlock)530 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
531 {
532     int result;
533 
534     if (!rwlock)
535         return EINVAL;
536     if (rwlock->attr == -1)
537         pthread_rwlock_init(rwlock, NULL);
538 
539     if ((result = pthread_mutex_lock(&rwlock->rw_mutex)) != 0)
540         return(result);
541 
542     while (rwlock->rw_refcount != 0)
543     {
544         rwlock->rw_nwaitwriters++;
545         /* rw_mutex will be released when waiting for rw_condwriters */
546         result = pthread_cond_wait(&rwlock->rw_condwriters, &rwlock->rw_mutex);
547         /* rw_mutex should have been taken again when returned from waiting */
548         rwlock->rw_nwaitwriters--;
549 
550         if (result != 0)
551             break;
552     }
553 
554     if (result == 0)
555         rwlock->rw_refcount = -1;
556 
557     pthread_mutex_unlock(&rwlock->rw_mutex);
558 
559     return(result);
560 }
561 RTM_EXPORT(pthread_rwlock_wrlock);
562 
563