From 00a89ee52f6d007864e2cd3f7016e1d5d29b6aff Mon Sep 17 00:00:00 2001 From: Cla Galliard Date: Mon, 13 May 2024 08:22:51 +0200 Subject: [PATCH 1/2] posix: pthread: implement non-standard try-join and timed-join These functions can be used to join pthreads in a non-standard way. The function pthread_tryjoin will not block and simply test whether the thread has exited already. The function pthread_timed_join will only block until the specified time. The functions are wrappers for calling the k_thread_join with timeout K_NO_WAIT and with a specific timeout as opposed to calling it with K_FOREVER. Signed-off-by: Cla Galliard --- include/zephyr/posix/pthread.h | 2 ++ lib/posix/options/pthread.c | 61 +++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/include/zephyr/posix/pthread.h b/include/zephyr/posix/pthread.h index 4a5e7046f17d6..685a8d9a9316e 100644 --- a/include/zephyr/posix/pthread.h +++ b/include/zephyr/posix/pthread.h @@ -429,6 +429,8 @@ int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched); int pthread_once(pthread_once_t *once, void (*initFunc)(void)); #endif FUNC_NORETURN void pthread_exit(void *retval); +int pthread_timedjoin_np(pthread_t thread, void **status, const struct timespec *abstime); +int pthread_tryjoin_np(pthread_t thread, void **status); int pthread_join(pthread_t thread, void **status); int pthread_cancel(pthread_t pthread); int pthread_detach(pthread_t thread); diff --git a/lib/posix/options/pthread.c b/lib/posix/options/pthread.c index 4ee2851dcdbff..c54a061f9e76b 100644 --- a/lib/posix/options/pthread.c +++ b/lib/posix/options/pthread.c @@ -81,6 +81,7 @@ BUILD_ASSERT((PTHREAD_CANCEL_ENABLE == 0 || PTHREAD_CANCEL_DISABLE == 0) && BUILD_ASSERT(CONFIG_POSIX_PTHREAD_ATTR_STACKSIZE_BITS + CONFIG_POSIX_PTHREAD_ATTR_GUARDSIZE_BITS <= 32); +int64_t timespec_to_timeoutms(const struct timespec *abstime); static void posix_thread_recycle(void); static sys_dlist_t posix_thread_q[] = { SYS_DLIST_STATIC_INIT(&posix_thread_q[POSIX_THREAD_READY_Q]), @@ -1061,12 +1062,7 @@ void pthread_exit(void *retval) CODE_UNREACHABLE; } -/** - * @brief Wait for a thread termination. - * - * See IEEE 1003.1 - */ -int pthread_join(pthread_t pthread, void **status) +static int pthread_timedjoin_internal(pthread_t pthread, void **status, k_timeout_t timeout) { int ret = ESRCH; struct posix_thread *t = NULL; @@ -1115,8 +1111,19 @@ int pthread_join(pthread_t pthread, void **status) break; } - ret = k_thread_join(&t->thread, K_FOREVER); - /* other possibilities? */ + ret = k_thread_join(&t->thread, timeout); + if (ret != 0) { + /* when joining failed, ensure that the thread can be joined later */ + SYS_SEM_LOCK(&pthread_pool_lock) { + t->attr.detachstate = PTHREAD_CREATE_JOINABLE; + } + } + if (ret == -EBUSY) { + return EBUSY; + } else if (ret == -EAGAIN) { + return ETIMEDOUT; + } + /* Can only be ok or -EDEADLK, which should never occur for pthreads */ __ASSERT_NO_MSG(ret == 0); LOG_DBG("Joined pthread %p", &t->thread); @@ -1131,6 +1138,44 @@ int pthread_join(pthread_t pthread, void **status) return 0; } +/** + * @brief Await a thread termination with timeout. + * + * Non-portable GNU extension of IEEE 1003.1 + */ +int pthread_timedjoin_np(pthread_t pthread, void **status, const struct timespec *abstime) +{ + if (abstime == NULL) { + return EINVAL; + } + + if (abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= NSEC_PER_SEC) { + return EINVAL; + } + + return pthread_timedjoin_internal(pthread, status, K_MSEC(timespec_to_timeoutms(abstime))); +} + +/** + * @brief Check a thread for termination. + * + * Non-portable GNU extension of IEEE 1003.1 + */ +int pthread_tryjoin_np(pthread_t pthread, void **status) +{ + return pthread_timedjoin_internal(pthread, status, K_NO_WAIT); +} + +/** + * @brief Await a thread termination. + * + * See IEEE 1003.1 + */ +int pthread_join(pthread_t pthread, void **status) +{ + return pthread_timedjoin_internal(pthread, status, K_FOREVER); +} + /** * @brief Detach a thread. * From 3bbce6e5312407477777140f72b4e08a03662b18 Mon Sep 17 00:00:00 2001 From: Cla Galliard Date: Sat, 1 Jun 2024 17:44:41 +0200 Subject: [PATCH 2/2] tests/posix: Add test for pthread_timed_join and pthread_try_join Test for the non-standard but useful pthread_timed_join- and pthread_try_join-functions. Signed-off-by: Cla Galliard --- tests/posix/common/src/pthread.c | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/posix/common/src/pthread.c b/tests/posix/common/src/pthread.c index 4fea9ed60ca51..8b99f15b88324 100644 --- a/tests/posix/common/src/pthread.c +++ b/tests/posix/common/src/pthread.c @@ -143,6 +143,14 @@ static void *thread_top_exec(void *p1) return NULL; } +static void *timedjoin_thread(void *p1) +{ + int sleep_duration_ms = POINTER_TO_INT(p1); + + usleep(USEC_PER_MSEC * sleep_duration_ms); + return NULL; +} + static int bounce_test_done(void) { int i; @@ -371,6 +379,69 @@ ZTEST(pthread, test_pthread_termination) zassert_equal(ret, ESRCH, "cancelled a terminated thread!"); } +ZTEST(pthread, test_pthread_tryjoin) +{ + pthread_t th = {0}; + int sleep_duration_ms = 200; + void *retval; + + /* Creating a thread that exits after 200ms*/ + zassert_ok(pthread_create(&th, NULL, timedjoin_thread, INT_TO_POINTER(sleep_duration_ms))); + + /* Attempting to join, when thread is still running, should fail */ + usleep(USEC_PER_MSEC * sleep_duration_ms / 2); + zassert_equal(pthread_tryjoin_np(th, &retval), EBUSY); + + /* Sleep so thread will exit */ + usleep(USEC_PER_MSEC * sleep_duration_ms); + + /* Attempting to join without blocking should succeed now */ + zassert_ok(pthread_tryjoin_np(th, &retval)); +} + +ZTEST(pthread, test_pthread_timedjoin) +{ + pthread_t th = {0}; + int sleep_duration_ms = 200; + void *ret; + struct timespec not_done; + struct timespec done; + struct timespec invalid[] = { + [0] = {.tv_sec = -1}, + [1] = {.tv_nsec = -1}, + [2] = {.tv_nsec = NSEC_PER_SEC}, + }; + + /* setup timespecs when the thread is still running and when it is done */ + clock_gettime(CLOCK_MONOTONIC, ¬_done); + clock_gettime(CLOCK_MONOTONIC, &done); + not_done.tv_nsec += sleep_duration_ms / 2 * NSEC_PER_MSEC; + done.tv_nsec += sleep_duration_ms * 1.5 * NSEC_PER_MSEC; + while (not_done.tv_nsec >= NSEC_PER_SEC) { + not_done.tv_sec++; + not_done.tv_nsec -= NSEC_PER_SEC; + } + while (done.tv_nsec >= NSEC_PER_SEC) { + done.tv_sec++; + done.tv_nsec -= NSEC_PER_SEC; + } + + /* Creating a thread that exits after 200ms*/ + zassert_ok(pthread_create(&th, NULL, timedjoin_thread, INT_TO_POINTER(sleep_duration_ms))); + + /* pthread_timedjoin-np must return -EINVAL for invalid struct timespecs */ + zassert_equal(pthread_timedjoin_np(th, &ret, NULL), EINVAL); + for (size_t i = 0; i < ARRAY_SIZE(invalid); ++i) { + zassert_equal(pthread_timedjoin_np(th, &ret, &invalid[i]), EINVAL); + } + + /* Attempting to join with a timeout, when the thread is still running should fail */ + zassert_equal(pthread_timedjoin_np(th, &ret, ¬_done), ETIMEDOUT); + + /* Attempting to join with a timeout, when the thread is done, should succeed */ + zassert_ok(pthread_timedjoin_np(th, &ret, &done)); +} + static void *create_thread1(void *p1) { /* do nothing */