From 2fdeaa882f7155342f508436b40b25c1c1f03695 Mon Sep 17 00:00:00 2001 From: Konstantin Kondrashov Date: Wed, 25 Jun 2025 13:36:53 +0300 Subject: [PATCH] feat(pthread): Adds timed locks for pthread_rwlock Closes https://github.com/espressif/esp-idf/issues/16099 --- components/pthread/pthread_rwlock.c | 90 +++++++++++++++++- .../main/test_pthread_rwlock.c | 95 ++++++++++++++++++- docs/en/api-reference/system/pthread.rst | 2 + docs/zh_CN/api-reference/system/pthread.rst | 2 + 4 files changed, 187 insertions(+), 2 deletions(-) diff --git a/components/pthread/pthread_rwlock.c b/components/pthread/pthread_rwlock.c index 846225562b..ca9d268045 100644 --- a/components/pthread/pthread_rwlock.c +++ b/components/pthread/pthread_rwlock.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,7 @@ #include #include #include +#include #include "esp_err.h" #include "esp_attr.h" #include "sys/queue.h" @@ -311,6 +312,93 @@ int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) return 0; } +int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, const struct timespec *abstime) +{ + esp_pthread_rwlock_t *esp_rwlock; + int res; + + res = checkrw_lock(rwlock); + if (res != 0) { + return res; + } + + esp_rwlock = (esp_pthread_rwlock_t *)*rwlock; + res = pthread_mutex_lock(&esp_rwlock->resource_mutex); + if (res != 0) { + return res; + } + + // If there are active readers or active writers, we go into the timed wait path + // otherwise, we go into the immediate success path. + if (esp_rwlock->active_readers || esp_rwlock->active_writers) { + // The timed wait path + if (!abstime || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) { + // The validity of the abstime parameter need be checked + // if the lock can not be immediately acquired (see Immediate success path). + pthread_mutex_unlock(&esp_rwlock->resource_mutex); + return EINVAL; + } + + esp_rwlock->waiting_writers++; + while (esp_rwlock->active_readers > 0 || esp_rwlock->active_writers > 0) { + res = pthread_cond_timedwait(&esp_rwlock->cv, &esp_rwlock->resource_mutex, abstime); + if (res == ETIMEDOUT) { + esp_rwlock->waiting_writers--; + pthread_mutex_unlock(&esp_rwlock->resource_mutex); + return ETIMEDOUT; + } + } + esp_rwlock->waiting_writers--; + } + + // Immediate success path. + esp_rwlock->active_writers++; + pthread_mutex_unlock(&esp_rwlock->resource_mutex); + return 0; +} + +int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abstime) +{ + esp_pthread_rwlock_t *esp_rwlock; + int res; + + res = checkrw_lock(rwlock); + if (res != 0) { + return res; + } + + esp_rwlock = (esp_pthread_rwlock_t *)*rwlock; + res = pthread_mutex_lock(&esp_rwlock->resource_mutex); + if (res != 0) { + return res; + } + + // If there are active writers or waiting writers, we go into the timed wait path + // otherwise, we go into the immediate success path. + if (esp_rwlock->active_writers || esp_rwlock->waiting_writers) { + // The timed wait path + if (!abstime || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) { + // The validity of the abstime parameter need be checked + // if the lock can not be immediately acquired (see Immediate success path). + pthread_mutex_unlock(&esp_rwlock->resource_mutex); + return EINVAL; + } + + while (esp_rwlock->active_writers > 0 || esp_rwlock->waiting_writers > 0) { + res = pthread_cond_timedwait(&esp_rwlock->cv, &esp_rwlock->resource_mutex, abstime); + if (res == ETIMEDOUT) { + pthread_mutex_unlock(&esp_rwlock->resource_mutex); + return ETIMEDOUT; + } + } + } + + // Immediate success path. + esp_rwlock->active_readers++; + pthread_mutex_unlock(&esp_rwlock->resource_mutex); + return 0; +} + /* Hook function to force linking this file */ void pthread_include_pthread_rwlock_impl(void) { diff --git a/components/pthread/test_apps/pthread_unity_tests/main/test_pthread_rwlock.c b/components/pthread/test_apps/pthread_unity_tests/main/test_pthread_rwlock.c index 0f9349da30..b15b47853e 100644 --- a/components/pthread/test_apps/pthread_unity_tests/main/test_pthread_rwlock.c +++ b/components/pthread/test_apps/pthread_unity_tests/main/test_pthread_rwlock.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -8,6 +8,7 @@ #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -382,3 +383,95 @@ TEST_CASE("trywrlock lock statically initialized lock", "[pthread][rwlock]") TEST_ASSERT_EQUAL_INT(pthread_rwlock_unlock(&rwlock), 0); TEST_ASSERT_EQUAL_INT(pthread_rwlock_destroy(&rwlock), 0); } + +static struct timespec get_abstime_ms(int ms) +{ + struct timespec ts; + struct timeval tv; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + ms / 1000; + ts.tv_nsec = (tv.tv_usec * 1000) + (ms % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000; + } + return ts; +} + +TEST_CASE("pthread_rwlock_timedrdlock invalid params", "[pthread][rwlock][timed]") +{ + struct timespec ts = get_abstime_ms(100); + TEST_ASSERT_EQUAL_INT(EINVAL, pthread_rwlock_timedrdlock(NULL, &ts)); + + pthread_rwlock_t rwlock = 0; + TEST_ASSERT_EQUAL_INT(EINVAL, pthread_rwlock_timedrdlock(&rwlock, &ts)); + + ts.tv_nsec = 1000000000; // invalid nanoseconds + pthread_rwlock_t rwlock2; + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_init(&rwlock2, NULL)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_wrlock(&rwlock2)); + TEST_ASSERT_EQUAL_INT(EINVAL, pthread_rwlock_timedrdlock(&rwlock2, &ts)); // invalid nanoseconds + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_unlock(&rwlock2)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_destroy(&rwlock2)); +} + +TEST_CASE("pthread_rwlock_timedwrlock invalid params", "[pthread][rwlock][timed]") +{ + struct timespec ts = get_abstime_ms(100); + TEST_ASSERT_EQUAL_INT(EINVAL, pthread_rwlock_timedwrlock(NULL, &ts)); + + pthread_rwlock_t rwlock = 0; + TEST_ASSERT_EQUAL_INT(EINVAL, pthread_rwlock_timedwrlock(&rwlock, &ts)); + + ts.tv_nsec = -1; // invalid nanoseconds + pthread_rwlock_t rwlock2; + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_init(&rwlock2, NULL)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_timedwrlock(&rwlock2, &ts)); + TEST_ASSERT_EQUAL_INT(EINVAL, pthread_rwlock_timedwrlock(&rwlock2, &ts)); // invalid nanoseconds + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_unlock(&rwlock2)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_destroy(&rwlock2)); +} + +TEST_CASE("pthread_rwlock_timedrdlock succeeds immediately", "[pthread][rwlock][timed]") +{ + pthread_rwlock_t rwlock; + struct timespec ts = get_abstime_ms(200); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_init(&rwlock, NULL)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_timedrdlock(&rwlock, &ts)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_unlock(&rwlock)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_destroy(&rwlock)); +} + +TEST_CASE("pthread_rwlock_timedwrlock succeeds immediately", "[pthread][rwlock][timed]") +{ + pthread_rwlock_t rwlock; + struct timespec ts = get_abstime_ms(200); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_init(&rwlock, NULL)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_timedwrlock(&rwlock, &ts)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_unlock(&rwlock)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_destroy(&rwlock)); +} + +TEST_CASE("pthread_rwlock_timedrdlock times out if writer holds lock", "[pthread][rwlock][timed]") +{ + pthread_rwlock_t rwlock; + pthread_rwlock_init(&rwlock, NULL); + + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_wrlock(&rwlock)); + struct timespec ts = get_abstime_ms(100); + TEST_ASSERT_EQUAL_INT(ETIMEDOUT, pthread_rwlock_timedrdlock(&rwlock, &ts)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_unlock(&rwlock)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_destroy(&rwlock)); +} + +TEST_CASE("pthread_rwlock_timedwrlock times out if reader holds lock", "[pthread][rwlock][timed]") +{ + pthread_rwlock_t rwlock; + pthread_rwlock_init(&rwlock, NULL); + + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_rdlock(&rwlock)); + struct timespec ts = get_abstime_ms(100); + TEST_ASSERT_EQUAL_INT(ETIMEDOUT, pthread_rwlock_timedwrlock(&rwlock, &ts)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_unlock(&rwlock)); + TEST_ASSERT_EQUAL_INT(0, pthread_rwlock_destroy(&rwlock)); +} diff --git a/docs/en/api-reference/system/pthread.rst b/docs/en/api-reference/system/pthread.rst index 894e1a659e..148344181e 100644 --- a/docs/en/api-reference/system/pthread.rst +++ b/docs/en/api-reference/system/pthread.rst @@ -154,6 +154,8 @@ The following API functions of the POSIX reader-writer locks specification are i * `pthread_rwlock_wrlock() `_ * `pthread_rwlock_trywrlock() `_ * `pthread_rwlock_unlock() `_ +* `pthread_rwlock_timedwrlock() `_ +* `pthread_rwlock_timedrdlock() `_ The static initializer constant ``PTHREAD_RWLOCK_INITIALIZER`` is supported. diff --git a/docs/zh_CN/api-reference/system/pthread.rst b/docs/zh_CN/api-reference/system/pthread.rst index 48af7bf9b8..71d5fc6afe 100644 --- a/docs/zh_CN/api-reference/system/pthread.rst +++ b/docs/zh_CN/api-reference/system/pthread.rst @@ -154,6 +154,8 @@ ESP-IDF 中实现了 POSIX 读写锁规范的以下 API 函数: * `pthread_rwlock_wrlock() `_ * `pthread_rwlock_trywrlock() `_ * `pthread_rwlock_unlock() `_ +* `pthread_rwlock_timedwrlock() `_ +* `pthread_rwlock_timedrdlock() `_ 支持静态初始化器常量 ``PTHREAD_RWLOCK_INITIALIZER``。