diff --git a/include/pthread_np.h b/include/pthread_np.h --- a/include/pthread_np.h +++ b/include/pthread_np.h @@ -65,6 +65,7 @@ void pthread_suspend_all_np(void); int pthread_suspend_np(pthread_t); int pthread_timedjoin_np(pthread_t, void **, const struct timespec *); +int pthread_tryjoin_np(pthread_t, void **); __END_DECLS #endif diff --git a/lib/libthr/pthread.map b/lib/libthr/pthread.map --- a/lib/libthr/pthread.map +++ b/lib/libthr/pthread.map @@ -342,3 +342,7 @@ pthread_signals_unblock_np; pthread_sigqueue; }; + +FBSD_1.9 { + pthread_tryjoin_np; +}; diff --git a/lib/libthr/tests/Makefile b/lib/libthr/tests/Makefile --- a/lib/libthr/tests/Makefile +++ b/lib/libthr/tests/Makefile @@ -36,6 +36,7 @@ ATF_TESTS_C+= atfork_test ATF_TESTS_C+= umtx_op_test ATF_TESTS_C+= pthread_sigqueue_test +ATF_TESTS_C+= pthread_tryjoin_test LIBADD+= pthread LIBADD.fpu_test+= m diff --git a/lib/libthr/tests/pthread_tryjoin_test.c b/lib/libthr/tests/pthread_tryjoin_test.c new file mode 100644 --- /dev/null +++ b/lib/libthr/tests/pthread_tryjoin_test.c @@ -0,0 +1,62 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2025 The FreeBSD Foundation + * + * This software was developed by Konstantin Belousov + * under sponsorship from the FreeBSD Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static atomic_int finish; + +static void * +thr_fun(void *arg) +{ + while (atomic_load_explicit(&finish, memory_order_relaxed) != 1) + sleep(1); + atomic_store_explicit(&finish, 2, memory_order_relaxed); + return (arg); +} + +ATF_TC(pthread_tryjoin); +ATF_TC_HEAD(pthread_tryjoin, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Checks pthread_tryjoin(3)"); +} + +ATF_TC_BODY(pthread_tryjoin, tc) +{ + pthread_t thr; + void *retval; + int error, x; + + error = pthread_create(&thr, NULL, thr_fun, &x); + ATF_REQUIRE_EQ(error, 0); + + error = pthread_tryjoin_np(thr, &retval); + ATF_REQUIRE_EQ(error, EBUSY); + + atomic_store_explicit(&finish, 1, memory_order_relaxed); + while (atomic_load_explicit(&finish, memory_order_relaxed) != 2) + sleep(1); + + error = pthread_tryjoin_np(thr, &retval); + ATF_REQUIRE_EQ(error, 0); + ATF_REQUIRE_EQ(retval, &x); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, pthread_tryjoin); + return (atf_no_error()); +} diff --git a/lib/libthr/thread/thr_join.c b/lib/libthr/thread/thr_join.c --- a/lib/libthr/thread/thr_join.c +++ b/lib/libthr/thread/thr_join.c @@ -34,29 +34,39 @@ #include "thr_private.h" int _pthread_peekjoin_np(pthread_t pthread, void **thread_return); +int _pthread_tryjoin_np(pthread_t pthread, void **thread_return); int _pthread_timedjoin_np(pthread_t pthread, void **thread_return, const struct timespec *abstime); -static int join_common(pthread_t, void **, const struct timespec *, bool peek); +static int join_common(pthread_t, void **, const struct timespec *, bool peek, + bool try); __weak_reference(_thr_join, pthread_join); __weak_reference(_thr_join, _pthread_join); __weak_reference(_pthread_timedjoin_np, pthread_timedjoin_np); __weak_reference(_pthread_peekjoin_np, pthread_peekjoin_np); +__weak_reference(_pthread_tryjoin_np, pthread_tryjoin_np); -static void backout_join(void *arg) +static void +backout_join(struct pthread *pthread, struct pthread *curthread) { - struct pthread *pthread = (struct pthread *)arg; - struct pthread *curthread = _get_curthread(); - THR_THREAD_LOCK(curthread, pthread); pthread->joiner = NULL; THR_THREAD_UNLOCK(curthread, pthread); } +static void +backout_join_pop(void *arg) +{ + struct pthread *pthread = (struct pthread *)arg; + struct pthread *curthread = _get_curthread(); + + backout_join(pthread, curthread); +} + int _thr_join(pthread_t pthread, void **thread_return) { - return (join_common(pthread, thread_return, NULL, false)); + return (join_common(pthread, thread_return, NULL, false, false)); } int @@ -67,13 +77,34 @@ abstime->tv_nsec >= 1000000000) return (EINVAL); - return (join_common(pthread, thread_return, abstime, false)); + return (join_common(pthread, thread_return, abstime, false, false)); } int _pthread_peekjoin_np(pthread_t pthread, void **thread_return) { - return (join_common(pthread, thread_return, NULL, true)); + return (join_common(pthread, thread_return, NULL, true, false)); +} + +int +_pthread_tryjoin_np(pthread_t pthread, void **thread_return) +{ + return (join_common(pthread, thread_return, NULL, false, true)); +} + +static void +join_common_joined(struct pthread *pthread, struct pthread *curthread, + void **thread_return) +{ + void *tmp; + + tmp = pthread->ret; + pthread->flags |= THR_FLAGS_DETACHED; + pthread->joiner = NULL; + _thr_try_gc(curthread, pthread); /* thread lock released */ + + if (thread_return != NULL) + *thread_return = tmp; } /* @@ -82,11 +113,10 @@ */ static int join_common(pthread_t pthread, void **thread_return, - const struct timespec *abstime, bool peek) + const struct timespec *abstime, bool peek, bool try) { struct pthread *curthread = _get_curthread(); struct timespec ts, ts2, *tsp; - void *tmp; long tid; int ret; @@ -120,12 +150,22 @@ return (ret); } + /* Only try to join. */ + if (try) { + if (pthread->tid != TID_TERMINATED) { + THR_THREAD_UNLOCK(curthread, pthread); + return (EBUSY); + } + join_common_joined(pthread, curthread, thread_return); + return (0); + } + /* Set the running thread to be the joiner: */ pthread->joiner = curthread; THR_THREAD_UNLOCK(curthread, pthread); - THR_CLEANUP_PUSH(curthread, backout_join, pthread); + THR_CLEANUP_PUSH(curthread, backout_join_pop, pthread); _thr_cancel_enter(curthread); tid = pthread->tid; @@ -149,20 +189,12 @@ _thr_cancel_leave(curthread, 0); THR_CLEANUP_POP(curthread, 0); - if (ret == ETIMEDOUT) { - THR_THREAD_LOCK(curthread, pthread); - pthread->joiner = NULL; - THR_THREAD_UNLOCK(curthread, pthread); + if (ret == ETIMEDOUT || ret == EBUSY) { + backout_join(pthread, curthread); } else { ret = 0; - tmp = pthread->ret; THR_THREAD_LOCK(curthread, pthread); - pthread->flags |= THR_FLAGS_DETACHED; - pthread->joiner = NULL; - _thr_try_gc(curthread, pthread); /* thread lock released */ - - if (thread_return != NULL) - *thread_return = tmp; + join_common_joined(pthread, curthread, thread_return); } return (ret); } diff --git a/share/man/man3/Makefile b/share/man/man3/Makefile --- a/share/man/man3/Makefile +++ b/share/man/man3/Makefile @@ -540,6 +540,7 @@ PTHREAD_MLINKS+=pthread_testcancel.3 pthread_setcancelstate.3 \ pthread_testcancel.3 pthread_setcanceltype.3 PTHREAD_MLINKS+=pthread_join.3 pthread_peekjoin_np.3 \ - pthread_join.3 pthread_timedjoin_np.3 + pthread_join.3 pthread_timedjoin_np.3 \ + pthread_join.3 pthread_tryjoin_np.3 .include diff --git a/share/man/man3/pthread_join.3 b/share/man/man3/pthread_join.3 --- a/share/man/man3/pthread_join.3 +++ b/share/man/man3/pthread_join.3 @@ -35,6 +35,7 @@ .Nm pthread_join , .Nm pthread_peekjoin_np , .Nm pthread_timedjoin_np +.Nm pthread_tryjoin_np .Nd inspect thread termination state .Sh LIBRARY .Lb libpthread @@ -54,6 +55,8 @@ .Fa "void **value_ptr" .Fa "const struct timespec *abstime" .Fc +.Ft int +.Fn pthread_tryjoin_np "pthread_t thread" "void **value_ptr" .Sh DESCRIPTION The .Fn pthread_join @@ -104,6 +107,13 @@ .Fn pthread_join family of functions again. .Pp +The +.Fn pthread_tryjoin_np +function joins the thread if it is already terminated, same as +.Fn pthread_join . +If the thread has not yet terminated, the function returns +.Er EBUSY . +.Pp A thread that has exited but remains unjoined counts against [_POSIX_THREAD_THREADS_MAX]. .Sh RETURN VALUES @@ -147,7 +157,9 @@ .Pp The .Fn pthread_peekjoin_np -function will also fail if: +and +.Fn pthread_tryjoin_np +functions will also fail if: .Bl -tag -width Er .It Bq Er EBUSY The specified thread has not yet exited. @@ -168,7 +180,15 @@ .Fx extension which first appeared in .Fx 6.1 . -Another extension, the +The .Fn pthread_peekjoin_np -function, first appearead in +function is a +.Fx +extension which first appearead in .Fx 13.0 . +The +.Fn pthread_tryjoin_np +function is a +.Fx +extension which first appearead in +.Fx 16.0 .