Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F177556705
D61253.1794713101.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
68 KB
Referenced Files
None
Subscribers
None
D61253.1794713101.diff
View Options
diff --git a/include/pthread.h b/include/pthread.h
--- a/include/pthread.h
+++ b/include/pthread.h
@@ -235,6 +235,11 @@
const pthread_mutexattr_t * __restrict, int * __restrict);
int pthread_mutexattr_settype(pthread_mutexattr_t *, int);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *, int);
+#if __POSIX_VISIBLE >= 202405
+int pthread_mutex_clocklock(pthread_mutex_t * __restrict __mutex,
+ clockid_t, const struct timespec * __restrict)
+ __trylocks_exclusive(0, *__mutex);
+#endif
int pthread_mutex_consistent(pthread_mutex_t * __mutex)
__requires_exclusive(*__mutex);
int pthread_mutex_destroy(pthread_mutex_t * __mutex)
@@ -252,6 +257,16 @@
int pthread_mutex_unlock(pthread_mutex_t * __mutex)
__unlocks(*__mutex);
int pthread_once(pthread_once_t *, void (*) (void));
+#if __POSIX_VISIBLE >= 202405
+int pthread_rwlock_clockrdlock(
+ pthread_rwlock_t * __restrict __rwlock,
+ clockid_t, const struct timespec * __restrict)
+ __trylocks_shared(0, *__rwlock);
+int pthread_rwlock_clockwrlock(
+ pthread_rwlock_t * __restrict __rwlock,
+ clockid_t, const struct timespec * __restrict)
+ __trylocks_exclusive(0, *__rwlock);
+#endif
int pthread_rwlock_destroy(pthread_rwlock_t * __rwlock)
__requires_unlocked(*__rwlock);
int pthread_rwlock_init(pthread_rwlock_t * __restrict __rwlock,
diff --git a/lib/libsys/_umtx_op.2 b/lib/libsys/_umtx_op.2
--- a/lib/libsys/_umtx_op.2
+++ b/lib/libsys/_umtx_op.2
@@ -190,36 +190,10 @@
request has both the
.Dv CVWAIT_CLOCKID
flag and the timeout specified.
-Valid clock identifiers are a subset of those for
-.Xr clock_gettime 2 :
-.Bl -bullet -compact
-.It
-.Dv CLOCK_MONOTONIC
-.It
-.Dv CLOCK_MONOTONIC_FAST
-.It
-.Dv CLOCK_MONOTONIC_PRECISE
-.It
-.Dv CLOCK_PROF
-.It
-.Dv CLOCK_REALTIME
-.It
-.Dv CLOCK_REALTIME_FAST
-.It
-.Dv CLOCK_REALTIME_PRECISE
-.It
-.Dv CLOCK_SECOND
-.It
-.Dv CLOCK_TAI
-.It
-.Dv CLOCK_UPTIME
-.It
-.Dv CLOCK_UPTIME_FAST
-.It
-.Dv CLOCK_UPTIME_PRECISE
-.It
-.Dv CLOCK_VIRTUAL
-.El
+Valid clock identifiers are the same as for the
+.Dv _clockid
+member of the
+.Vt struct _umtx_time .
.It Sy Reader/writer lock
.Bd -literal
struct urwlock {
@@ -357,15 +331,46 @@
member specifies the time when the timeout should occur.
Legal values for clock identifier
.Dv _clockid
-are shared with the
+are a subset of the valid values for the
.Fa clock_id
argument to the
.Xr clock_gettime 2
-function,
-and use the same underlying clocks.
+function, and use the same underlying clocks.
The specified clock is used to obtain the current time value.
Interval counting is always performed by the monotonic wall clock.
.Pp
+Currently accepted clocks are:
+.Bl -bullet -compact
+.It
+.Dv CLOCK_MONOTONIC
+.It
+.Dv CLOCK_MONOTONIC_FAST
+.It
+.Dv CLOCK_MONOTONIC_PRECISE
+.It
+.Dv CLOCK_PROCESS_CPUTIME_ID
+.It
+.Dv CLOCK_PROF
+.It
+.Dv CLOCK_REALTIME
+.It
+.Dv CLOCK_REALTIME_FAST
+.It
+.Dv CLOCK_REALTIME_PRECISE
+.It
+.Dv CLOCK_SECOND
+.It
+.Dv CLOCK_TAI
+.It
+.Dv CLOCK_UPTIME
+.It
+.Dv CLOCK_UPTIME_FAST
+.It
+.Dv CLOCK_UPTIME_PRECISE
+.It
+.Dv CLOCK_VIRTUAL
+.El
+.Pp
The
.Dv _flags
argument allows the following flags to further define the timeout behaviour:
diff --git a/lib/libthr/pthread.map b/lib/libthr/pthread.map
--- a/lib/libthr/pthread.map
+++ b/lib/libthr/pthread.map
@@ -345,7 +345,10 @@
FBSD_1.9 {
pthread_cond_clockwait;
+ pthread_mutex_clocklock;
pthread_query_np;
pthread_query_all_np;
+ pthread_rwlock_clockrdlock;
+ pthread_rwlock_clockwrlock;
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
@@ -38,6 +38,8 @@
ATF_TESTS_C+= pthread_sigqueue_test
ATF_TESTS_C+= pthread_tryjoin_test
ATF_TESTS_C+= pthread_query_test
+ATF_TESTS_C+= pthread_mutex_clocklock_test
+ATF_TESTS_C+= pthread_rwlock_clocklock_test
LIBADD+= pthread
LIBADD.fpu_test+= m
diff --git a/lib/libthr/tests/pthread_mutex_clocklock_test.c b/lib/libthr/tests/pthread_mutex_clocklock_test.c
new file mode 100644
--- /dev/null
+++ b/lib/libthr/tests/pthread_mutex_clocklock_test.c
@@ -0,0 +1,489 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2026 The FreeBSD Foundation
+ *
+ * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define TIMEOUT_MS 250
+
+static const struct test_clock {
+ clockid_t id;
+ const char *name;
+} accepted_clocks[] = {
+#define CLK(c) { .id = (c), .name = #c }
+ CLK(CLOCK_REALTIME),
+ CLK(CLOCK_REALTIME_PRECISE),
+ CLK(CLOCK_REALTIME_FAST),
+ CLK(CLOCK_MONOTONIC),
+ CLK(CLOCK_MONOTONIC_PRECISE),
+ CLK(CLOCK_MONOTONIC_FAST),
+ CLK(CLOCK_UPTIME),
+ CLK(CLOCK_UPTIME_PRECISE),
+ CLK(CLOCK_UPTIME_FAST),
+ CLK(CLOCK_SECOND),
+ CLK(CLOCK_VIRTUAL),
+ CLK(CLOCK_PROF),
+ CLK(CLOCK_PROCESS_CPUTIME_ID),
+ CLK(CLOCK_TAI),
+#undef CLK
+};
+
+struct locker {
+ pthread_mutex_t *mtx;
+ clockid_t clock;
+ struct timespec abstime;
+ int error;
+ bool reached; /* clock reached abstime on return */
+};
+
+/*
+ * Set *ts to the time msec milliseconds after the current time of
+ * the clock.
+ */
+static void
+deadline(clockid_t clock, long msec, struct timespec *ts)
+{
+ ATF_REQUIRE_INTEQ(0, clock_gettime(clock, ts));
+ ts->tv_sec += msec / 1000;
+ ts->tv_nsec += (msec % 1000) * 1000000;
+ if (ts->tv_nsec >= 1000000000) {
+ ts->tv_sec++;
+ ts->tv_nsec -= 1000000000;
+ }
+}
+
+static bool
+clock_reached(clockid_t clock, const struct timespec *ts)
+{
+ struct timespec now;
+
+ return (clock_gettime(clock, &now) == 0 &&
+ timespeccmp(&now, ts, >=));
+}
+
+static void
+mutex_init_attr(pthread_mutex_t *mtx, int type, int protocol)
+{
+ pthread_mutexattr_t attr;
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutexattr_init(&attr));
+ ATF_REQUIRE_INTEQ(0, pthread_mutexattr_settype(&attr, type));
+ ATF_REQUIRE_INTEQ(0, pthread_mutexattr_setprotocol(&attr, protocol));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_init(mtx, &attr));
+ ATF_REQUIRE_INTEQ(0, pthread_mutexattr_destroy(&attr));
+}
+
+static void *
+locker_thread(void *arg)
+{
+ struct locker *l;
+
+ l = arg;
+ l->error = pthread_mutex_clocklock(l->mtx, l->clock, &l->abstime);
+ l->reached = clock_reached(l->clock, &l->abstime);
+ if (l->error == 0)
+ (void)pthread_mutex_unlock(l->mtx);
+ return (NULL);
+}
+
+static void
+locker_start(struct locker *l, pthread_t *thr, pthread_mutex_t *mtx,
+ clockid_t clock, const struct timespec *abstime)
+{
+ l->mtx = mtx;
+ l->clock = clock;
+ l->abstime = *abstime;
+ l->error = -1;
+ l->reached = false;
+ ATF_REQUIRE_INTEQ(0, pthread_create(thr, NULL, locker_thread, l));
+}
+
+/*
+ * Call pthread_mutex_clocklock() from another thread, while the
+ * calling thread owns the mutex.
+ */
+static void
+clocklock_contended(pthread_mutex_t *mtx, clockid_t clock,
+ const struct timespec *abstime, struct locker *l)
+{
+ pthread_t thr;
+
+ locker_start(l, &thr, mtx, clock, abstime);
+ ATF_REQUIRE_INTEQ(0, pthread_join(thr, NULL));
+}
+
+static void
+check_timeout(int protocol, clockid_t clock)
+{
+ pthread_mutex_t mtx;
+ struct timespec ts;
+ struct locker l;
+
+ mutex_init_attr(&mtx, PTHREAD_MUTEX_DEFAULT, protocol);
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ deadline(clock, TIMEOUT_MS, &ts);
+ clocklock_contended(&mtx, clock, &ts, &l);
+ ATF_CHECK_INTEQ(ETIMEDOUT, l.error);
+ ATF_CHECK_MSG(l.reached, "returned before the timeout expired");
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_uncontended);
+ATF_TC_HEAD(clocklock_uncontended, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that an unowned mutex is locked without validating "
+ "the clock and the timeout");
+}
+
+ATF_TC_BODY(clocklock_uncontended, tc)
+{
+ pthread_mutex_t mtx;
+ struct timespec ts;
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_init(&mtx, NULL));
+
+ deadline(CLOCK_REALTIME, TIMEOUT_MS, &ts);
+ ATF_CHECK_INTEQ(0, pthread_mutex_clocklock(&mtx, CLOCK_REALTIME, &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+
+ deadline(CLOCK_MONOTONIC, TIMEOUT_MS, &ts);
+ ATF_CHECK_INTEQ(0, pthread_mutex_clocklock(&mtx, CLOCK_MONOTONIC,
+ &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+
+ ATF_CHECK_INTEQ(0, pthread_mutex_clocklock(&mtx,
+ CLOCK_THREAD_CPUTIME_ID, &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+
+ ts.tv_nsec = 1000000000;
+ ATF_CHECK_INTEQ(0, pthread_mutex_clocklock(&mtx, CLOCK_MONOTONIC,
+ &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_timeout_realtime);
+ATF_TC_HEAD(clocklock_timeout_realtime, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks the CLOCK_REALTIME timeout for a mutex owned by "
+ "another thread");
+}
+
+ATF_TC_BODY(clocklock_timeout_realtime, tc)
+{
+ check_timeout(PTHREAD_PRIO_NONE, CLOCK_REALTIME);
+}
+
+ATF_TC(clocklock_timeout_monotonic);
+ATF_TC_HEAD(clocklock_timeout_monotonic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks the CLOCK_MONOTONIC timeout for a mutex owned by "
+ "another thread");
+}
+
+ATF_TC_BODY(clocklock_timeout_monotonic, tc)
+{
+ check_timeout(PTHREAD_PRIO_NONE, CLOCK_MONOTONIC);
+}
+
+ATF_TC(clocklock_timeout_pi);
+ATF_TC_HEAD(clocklock_timeout_pi, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks the CLOCK_MONOTONIC timeout for a priority-inheritance "
+ "mutex owned by another thread");
+}
+
+ATF_TC_BODY(clocklock_timeout_pi, tc)
+{
+ check_timeout(PTHREAD_PRIO_INHERIT, CLOCK_MONOTONIC);
+}
+
+ATF_TC(clocklock_wakeup);
+ATF_TC_HEAD(clocklock_wakeup, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that a waiter gets the mutex when it is unlocked "
+ "before the timeout");
+}
+
+ATF_TC_BODY(clocklock_wakeup, tc)
+{
+ pthread_mutex_t mtx;
+ pthread_t thr;
+ struct timespec ts;
+ struct locker l;
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_init(&mtx, NULL));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ deadline(CLOCK_MONOTONIC, 60 * 1000, &ts);
+ locker_start(&l, &thr, &mtx, CLOCK_MONOTONIC, &ts);
+ usleep(100 * 1000);
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_join(thr, NULL));
+ ATF_CHECK_INTEQ(0, l.error);
+ ATF_CHECK_MSG(!l.reached, "returned after the timeout expired");
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_accepted_clocks);
+ATF_TC_HEAD(clocklock_accepted_clocks, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that all clocks accepted by _umtx_op(2) can be used");
+}
+
+ATF_TC_BODY(clocklock_accepted_clocks, tc)
+{
+ const struct test_clock *c;
+ pthread_mutex_t mtx;
+ struct timespec ts;
+ struct locker l;
+ u_int i;
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_init(&mtx, NULL));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ for (i = 0; i < nitems(accepted_clocks); i++) {
+ c = &accepted_clocks[i];
+
+ /*
+ * The deadline is the current time, so the wait times
+ * out right away. CLOCK_TAI cannot be read until the
+ * TAI-UTC offset is known.
+ */
+ if (clock_gettime(c->id, &ts) != 0) {
+ ATF_REQUIRE_MSG(c->id == CLOCK_TAI,
+ "clock_gettime(%s): %s", c->name, strerror(errno));
+ continue;
+ }
+ clocklock_contended(&mtx, c->id, &ts, &l);
+ ATF_CHECK_INTEQ_MSG(ETIMEDOUT, l.error, "%s", c->name);
+ }
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_rejected_clocks);
+ATF_TC_HEAD(clocklock_rejected_clocks, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that EINVAL is returned for unsupported clocks when "
+ "the mutex is owned by another thread");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+
+ATF_TC_BODY(clocklock_rejected_clocks, tc)
+{
+ static const int protocols[] = {
+ PTHREAD_PRIO_NONE,
+ PTHREAD_PRIO_INHERIT,
+ };
+ pthread_mutex_t mtx;
+ struct timespec ts;
+ struct locker l;
+ clockid_t clocks[4];
+ u_int i, j;
+
+ clocks[0] = CLOCK_THREAD_CPUTIME_ID;
+ clocks[1] = INT_MAX; /* not a clock */
+ ATF_REQUIRE_INTEQ(0, clock_getcpuclockid(getpid(), &clocks[2]));
+ ATF_REQUIRE_INTEQ(0, pthread_getcpuclockid(pthread_self(),
+ &clocks[3]));
+ deadline(CLOCK_REALTIME, TIMEOUT_MS, &ts);
+
+ for (i = 0; i < nitems(protocols); i++) {
+ mutex_init_attr(&mtx, PTHREAD_MUTEX_DEFAULT, protocols[i]);
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ for (j = 0; j < nitems(clocks); j++) {
+ clocklock_contended(&mtx, clocks[j], &ts, &l);
+ ATF_CHECK_INTEQ_MSG(EINVAL, l.error,
+ "protocol %d clock %d", protocols[i], clocks[j]);
+ }
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+ }
+}
+
+ATF_TC(clocklock_invalid_abstime);
+ATF_TC_HEAD(clocklock_invalid_abstime, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that EINVAL is returned for an invalid timeout when "
+ "the mutex is owned by another thread");
+}
+
+ATF_TC_BODY(clocklock_invalid_abstime, tc)
+{
+ pthread_mutex_t mtx;
+ struct timespec ts;
+ struct locker l;
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_init(&mtx, NULL));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ deadline(CLOCK_MONOTONIC, TIMEOUT_MS, &ts);
+ ts.tv_nsec = 1000000000;
+ clocklock_contended(&mtx, CLOCK_MONOTONIC, &ts, &l);
+ ATF_CHECK_INTEQ(EINVAL, l.error);
+ ts.tv_nsec = -1;
+ clocklock_contended(&mtx, CLOCK_MONOTONIC, &ts, &l);
+ ATF_CHECK_INTEQ(EINVAL, l.error);
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_zero_deadline);
+ATF_TC_HEAD(clocklock_zero_deadline, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that the absolute time zero is treated as expired");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+
+ATF_TC_BODY(clocklock_zero_deadline, tc)
+{
+ static const struct test_clock clocks[] = {
+ { .id = CLOCK_REALTIME, .name = "CLOCK_REALTIME" },
+ { .id = CLOCK_MONOTONIC, .name = "CLOCK_MONOTONIC" },
+ { .id = CLOCK_UPTIME, .name = "CLOCK_UPTIME" },
+ };
+ pthread_mutex_t mtx;
+ struct timespec ts;
+ struct locker l;
+ u_int i;
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_init(&mtx, NULL));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ for (i = 0; i < nitems(clocks); i++) {
+ clocklock_contended(&mtx, clocks[i].id, &ts, &l);
+ ATF_CHECK_INTEQ_MSG(ETIMEDOUT, l.error, "%s", clocks[i].name);
+ }
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_errorcheck_relock);
+ATF_TC_HEAD(clocklock_errorcheck_relock, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that relocking an owned error-checking mutex fails "
+ "with EDEADLK without waiting");
+}
+
+ATF_TC_BODY(clocklock_errorcheck_relock, tc)
+{
+ pthread_mutex_t mtx;
+ struct timespec ts;
+
+ mutex_init_attr(&mtx, PTHREAD_MUTEX_ERRORCHECK, PTHREAD_PRIO_NONE);
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ deadline(CLOCK_MONOTONIC, 60 * 1000, &ts);
+ ATF_CHECK_INTEQ(EDEADLK, pthread_mutex_clocklock(&mtx,
+ CLOCK_MONOTONIC, &ts));
+ ATF_CHECK_MSG(!clock_reached(CLOCK_MONOTONIC, &ts),
+ "waited for the timeout");
+
+ /* The call does not block, so the arguments are not validated. */
+ ATF_CHECK_INTEQ(EDEADLK, pthread_mutex_clocklock(&mtx,
+ CLOCK_THREAD_CPUTIME_ID, &ts));
+ ts.tv_nsec = 1000000000;
+ ATF_CHECK_INTEQ(EDEADLK, pthread_mutex_clocklock(&mtx,
+ CLOCK_MONOTONIC, &ts));
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_recursive_relock);
+ATF_TC_HEAD(clocklock_recursive_relock, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that relocking an owned recursive mutex succeeds "
+ "without validating the clock and the timeout");
+}
+
+ATF_TC_BODY(clocklock_recursive_relock, tc)
+{
+ pthread_mutex_t mtx;
+ struct timespec ts;
+
+ mutex_init_attr(&mtx, PTHREAD_MUTEX_RECURSIVE, PTHREAD_PRIO_NONE);
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ ts.tv_sec = 0;
+ ts.tv_nsec = 1000000000;
+ ATF_CHECK_INTEQ(0, pthread_mutex_clocklock(&mtx,
+ CLOCK_THREAD_CPUTIME_ID, &ts));
+ ATF_CHECK_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_CHECK_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_CHECK_INTEQ(EPERM, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TC(clocklock_normal_relock);
+ATF_TC_HEAD(clocklock_normal_relock, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that relocking an owned normal mutex waits for the "
+ "timeout");
+}
+
+ATF_TC_BODY(clocklock_normal_relock, tc)
+{
+ pthread_mutex_t mtx;
+ struct timespec ts;
+
+ mutex_init_attr(&mtx, PTHREAD_MUTEX_NORMAL, PTHREAD_PRIO_NONE);
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_lock(&mtx));
+ deadline(CLOCK_MONOTONIC, TIMEOUT_MS, &ts);
+ ATF_CHECK_INTEQ(ETIMEDOUT, pthread_mutex_clocklock(&mtx,
+ CLOCK_MONOTONIC, &ts));
+ ATF_CHECK_MSG(clock_reached(CLOCK_MONOTONIC, &ts),
+ "returned before the timeout expired");
+
+ ATF_CHECK_INTEQ(EINVAL, pthread_mutex_clocklock(&mtx,
+ CLOCK_THREAD_CPUTIME_ID, &ts));
+ ts.tv_nsec = 1000000000;
+ ATF_CHECK_INTEQ(EINVAL, pthread_mutex_clocklock(&mtx,
+ CLOCK_MONOTONIC, &ts));
+
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_unlock(&mtx));
+ ATF_REQUIRE_INTEQ(0, pthread_mutex_destroy(&mtx));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, clocklock_uncontended);
+ ATF_TP_ADD_TC(tp, clocklock_timeout_realtime);
+ ATF_TP_ADD_TC(tp, clocklock_timeout_monotonic);
+ ATF_TP_ADD_TC(tp, clocklock_timeout_pi);
+ ATF_TP_ADD_TC(tp, clocklock_wakeup);
+ ATF_TP_ADD_TC(tp, clocklock_accepted_clocks);
+ ATF_TP_ADD_TC(tp, clocklock_rejected_clocks);
+ ATF_TP_ADD_TC(tp, clocklock_invalid_abstime);
+ ATF_TP_ADD_TC(tp, clocklock_zero_deadline);
+ ATF_TP_ADD_TC(tp, clocklock_errorcheck_relock);
+ ATF_TP_ADD_TC(tp, clocklock_recursive_relock);
+ ATF_TP_ADD_TC(tp, clocklock_normal_relock);
+ return (atf_no_error());
+}
diff --git a/lib/libthr/tests/pthread_rwlock_clocklock_test.c b/lib/libthr/tests/pthread_rwlock_clocklock_test.c
new file mode 100644
--- /dev/null
+++ b/lib/libthr/tests/pthread_rwlock_clocklock_test.c
@@ -0,0 +1,528 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2026 The FreeBSD Foundation
+ *
+ * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <sys/param.h>
+#include <sys/time.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define TIMEOUT_MS 250
+
+static const struct test_clock {
+ clockid_t id;
+ const char *name;
+} accepted_clocks[] = {
+#define CLK(c) { .id = (c), .name = #c }
+ CLK(CLOCK_REALTIME),
+ CLK(CLOCK_REALTIME_PRECISE),
+ CLK(CLOCK_REALTIME_FAST),
+ CLK(CLOCK_MONOTONIC),
+ CLK(CLOCK_MONOTONIC_PRECISE),
+ CLK(CLOCK_MONOTONIC_FAST),
+ CLK(CLOCK_UPTIME),
+ CLK(CLOCK_UPTIME_PRECISE),
+ CLK(CLOCK_UPTIME_FAST),
+ CLK(CLOCK_SECOND),
+ CLK(CLOCK_VIRTUAL),
+ CLK(CLOCK_PROF),
+ CLK(CLOCK_PROCESS_CPUTIME_ID),
+ CLK(CLOCK_TAI),
+#undef CLK
+};
+
+struct locker {
+ pthread_rwlock_t *rw;
+ bool write;
+ clockid_t clock;
+ struct timespec abstime;
+ int error;
+ bool reached; /* clock reached abstime on return */
+};
+
+/*
+ * Set *ts to the time msec milliseconds after the current time of
+ * the clock.
+ */
+static void
+deadline(clockid_t clock, long msec, struct timespec *ts)
+{
+ ATF_REQUIRE_INTEQ(0, clock_gettime(clock, ts));
+ ts->tv_sec += msec / 1000;
+ ts->tv_nsec += (msec % 1000) * 1000000;
+ if (ts->tv_nsec >= 1000000000) {
+ ts->tv_sec++;
+ ts->tv_nsec -= 1000000000;
+ }
+}
+
+static bool
+clock_reached(clockid_t clock, const struct timespec *ts)
+{
+ struct timespec now;
+
+ return (clock_gettime(clock, &now) == 0 &&
+ timespeccmp(&now, ts, >=));
+}
+
+static int
+clocklock(pthread_rwlock_t *rw, bool write, clockid_t clock,
+ const struct timespec *abstime)
+{
+ return (write ? pthread_rwlock_clockwrlock(rw, clock, abstime) :
+ pthread_rwlock_clockrdlock(rw, clock, abstime));
+}
+
+/*
+ * Lock the rwlock so that the lock for the given mode cannot be
+ * granted: a reader is blocked by a writer, and a writer by a reader.
+ */
+static void
+block_mode(pthread_rwlock_t *rw, bool write)
+{
+ if (write)
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_rdlock(rw));
+ else
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_wrlock(rw));
+}
+
+static void *
+locker_thread(void *arg)
+{
+ struct locker *l;
+
+ l = arg;
+ l->error = clocklock(l->rw, l->write, l->clock, &l->abstime);
+ l->reached = clock_reached(l->clock, &l->abstime);
+ if (l->error == 0)
+ (void)pthread_rwlock_unlock(l->rw);
+ return (NULL);
+}
+
+static void
+locker_start(struct locker *l, pthread_t *thr, pthread_rwlock_t *rw,
+ bool write, clockid_t clock, const struct timespec *abstime)
+{
+ l->rw = rw;
+ l->write = write;
+ l->clock = clock;
+ l->abstime = *abstime;
+ l->error = -1;
+ l->reached = false;
+ ATF_REQUIRE_INTEQ(0, pthread_create(thr, NULL, locker_thread, l));
+}
+
+/*
+ * Try to lock from another thread, while the calling thread holds
+ * the lock in the conflicting mode.
+ */
+static void
+clocklock_contended(pthread_rwlock_t *rw, bool write, clockid_t clock,
+ const struct timespec *abstime, struct locker *l)
+{
+ pthread_t thr;
+
+ locker_start(l, &thr, rw, write, clock, abstime);
+ ATF_REQUIRE_INTEQ(0, pthread_join(thr, NULL));
+}
+
+static const char *
+mode_name(bool write)
+{
+ return (write ? "pthread_rwlock_clockwrlock" :
+ "pthread_rwlock_clockrdlock");
+}
+
+static void
+check_uncontended(bool write)
+{
+ pthread_rwlock_t rw;
+ struct timespec ts;
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+
+ deadline(CLOCK_REALTIME, TIMEOUT_MS, &ts);
+ ATF_CHECK_INTEQ(0, clocklock(&rw, write, CLOCK_REALTIME, &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+
+ deadline(CLOCK_MONOTONIC, TIMEOUT_MS, &ts);
+ ATF_CHECK_INTEQ(0, clocklock(&rw, write, CLOCK_MONOTONIC, &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+
+ /* Not validated when the lock is granted without blocking. */
+ ATF_CHECK_INTEQ(0, clocklock(&rw, write, CLOCK_THREAD_CPUTIME_ID,
+ &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+
+ ts.tv_nsec = 1000000000;
+ ATF_CHECK_INTEQ(0, clocklock(&rw, write, CLOCK_MONOTONIC, &ts));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+static void
+check_timeout(bool write, clockid_t clock)
+{
+ pthread_rwlock_t rw;
+ struct timespec ts;
+ struct locker l;
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+ block_mode(&rw, write);
+ deadline(clock, TIMEOUT_MS, &ts);
+ clocklock_contended(&rw, write, clock, &ts, &l);
+ ATF_CHECK_INTEQ(ETIMEDOUT, l.error);
+ ATF_CHECK_MSG(l.reached, "returned before the timeout expired");
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+static void
+check_wakeup(bool write)
+{
+ pthread_rwlock_t rw;
+ pthread_t thr;
+ struct timespec ts;
+ struct locker l;
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+ block_mode(&rw, write);
+ deadline(CLOCK_MONOTONIC, 60 * 1000, &ts);
+ locker_start(&l, &thr, &rw, write, CLOCK_MONOTONIC, &ts);
+ usleep(100 * 1000);
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+ ATF_REQUIRE_INTEQ(0, pthread_join(thr, NULL));
+ ATF_CHECK_INTEQ(0, l.error);
+ ATF_CHECK_MSG(!l.reached, "returned after the timeout expired");
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+static void
+check_accepted_clocks(bool write)
+{
+ const struct test_clock *c;
+ pthread_rwlock_t rw;
+ struct timespec ts;
+ struct locker l;
+ u_int i;
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+ block_mode(&rw, write);
+ for (i = 0; i < nitems(accepted_clocks); i++) {
+ c = &accepted_clocks[i];
+
+ /*
+ * The deadline is the current time, so the wait times
+ * out right away. CLOCK_TAI cannot be read until the
+ * TAI-UTC offset is known.
+ */
+ if (clock_gettime(c->id, &ts) != 0) {
+ ATF_REQUIRE_MSG(c->id == CLOCK_TAI,
+ "clock_gettime(%s): %s", c->name, strerror(errno));
+ continue;
+ }
+ clocklock_contended(&rw, write, c->id, &ts, &l);
+ ATF_CHECK_INTEQ_MSG(ETIMEDOUT, l.error, "%s %s",
+ mode_name(write), c->name);
+ }
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+static void
+check_rejected_clocks(bool write)
+{
+ pthread_rwlock_t rw;
+ struct timespec ts;
+ struct locker l;
+ clockid_t clocks[4];
+ u_int i;
+
+ clocks[0] = CLOCK_THREAD_CPUTIME_ID;
+ clocks[1] = INT_MAX; /* not a clock */
+ ATF_REQUIRE_INTEQ(0, clock_getcpuclockid(getpid(), &clocks[2]));
+ ATF_REQUIRE_INTEQ(0, pthread_getcpuclockid(pthread_self(),
+ &clocks[3]));
+ deadline(CLOCK_REALTIME, TIMEOUT_MS, &ts);
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+ block_mode(&rw, write);
+ for (i = 0; i < nitems(clocks); i++) {
+ clocklock_contended(&rw, write, clocks[i], &ts, &l);
+ ATF_CHECK_INTEQ_MSG(EINVAL, l.error, "%s clock %d",
+ mode_name(write), clocks[i]);
+ }
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+static void
+check_invalid_abstime(bool write)
+{
+ pthread_rwlock_t rw;
+ struct timespec ts;
+ struct locker l;
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+ block_mode(&rw, write);
+ deadline(CLOCK_MONOTONIC, TIMEOUT_MS, &ts);
+ ts.tv_nsec = 1000000000;
+ clocklock_contended(&rw, write, CLOCK_MONOTONIC, &ts, &l);
+ ATF_CHECK_INTEQ_MSG(EINVAL, l.error, "%s", mode_name(write));
+ ts.tv_nsec = -1;
+ clocklock_contended(&rw, write, CLOCK_MONOTONIC, &ts, &l);
+ ATF_CHECK_INTEQ_MSG(EINVAL, l.error, "%s", mode_name(write));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+static void
+check_zero_deadline(bool write)
+{
+ static const struct test_clock clocks[] = {
+ { .id = CLOCK_REALTIME, .name = "CLOCK_REALTIME" },
+ { .id = CLOCK_MONOTONIC, .name = "CLOCK_MONOTONIC" },
+ { .id = CLOCK_UPTIME, .name = "CLOCK_UPTIME" },
+ };
+ pthread_rwlock_t rw;
+ struct timespec ts;
+ struct locker l;
+ u_int i;
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+ block_mode(&rw, write);
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ for (i = 0; i < nitems(clocks); i++) {
+ clocklock_contended(&rw, write, clocks[i].id, &ts, &l);
+ ATF_CHECK_INTEQ_MSG(ETIMEDOUT, l.error, "%s %s",
+ mode_name(write), clocks[i].name);
+ }
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+ATF_TC(clockrdlock_uncontended);
+ATF_TC_HEAD(clockrdlock_uncontended, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that a free rwlock is read-locked without validating "
+ "the clock and the timeout");
+}
+
+ATF_TC_BODY(clockrdlock_uncontended, tc)
+{
+ check_uncontended(false);
+}
+
+ATF_TC(clockwrlock_uncontended);
+ATF_TC_HEAD(clockwrlock_uncontended, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that a free rwlock is write-locked without validating "
+ "the clock and the timeout");
+}
+
+ATF_TC_BODY(clockwrlock_uncontended, tc)
+{
+ check_uncontended(true);
+}
+
+ATF_TC(clockrdlock_timeout_realtime);
+ATF_TC_HEAD(clockrdlock_timeout_realtime, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks the CLOCK_REALTIME timeout for a read lock");
+}
+
+ATF_TC_BODY(clockrdlock_timeout_realtime, tc)
+{
+ check_timeout(false, CLOCK_REALTIME);
+}
+
+ATF_TC(clockrdlock_timeout_monotonic);
+ATF_TC_HEAD(clockrdlock_timeout_monotonic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks the CLOCK_MONOTONIC timeout for a read lock");
+}
+
+ATF_TC_BODY(clockrdlock_timeout_monotonic, tc)
+{
+ check_timeout(false, CLOCK_MONOTONIC);
+}
+
+ATF_TC(clockwrlock_timeout_realtime);
+ATF_TC_HEAD(clockwrlock_timeout_realtime, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks the CLOCK_REALTIME timeout for a write lock");
+}
+
+ATF_TC_BODY(clockwrlock_timeout_realtime, tc)
+{
+ check_timeout(true, CLOCK_REALTIME);
+}
+
+ATF_TC(clockwrlock_timeout_monotonic);
+ATF_TC_HEAD(clockwrlock_timeout_monotonic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks the CLOCK_MONOTONIC timeout for a write lock");
+}
+
+ATF_TC_BODY(clockwrlock_timeout_monotonic, tc)
+{
+ check_timeout(true, CLOCK_MONOTONIC);
+}
+
+ATF_TC(clockrdlock_wakeup);
+ATF_TC_HEAD(clockrdlock_wakeup, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that a reader gets the lock when the writer unlocks "
+ "it before the timeout");
+}
+
+ATF_TC_BODY(clockrdlock_wakeup, tc)
+{
+ check_wakeup(false);
+}
+
+ATF_TC(clockwrlock_wakeup);
+ATF_TC_HEAD(clockwrlock_wakeup, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that a writer gets the lock when the reader unlocks "
+ "it before the timeout");
+}
+
+ATF_TC_BODY(clockwrlock_wakeup, tc)
+{
+ check_wakeup(true);
+}
+
+ATF_TC(clocklock_accepted_clocks);
+ATF_TC_HEAD(clocklock_accepted_clocks, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that all clocks accepted by _umtx_op(2) can be used");
+}
+
+ATF_TC_BODY(clocklock_accepted_clocks, tc)
+{
+ check_accepted_clocks(false);
+ check_accepted_clocks(true);
+}
+
+ATF_TC(clocklock_rejected_clocks);
+ATF_TC_HEAD(clocklock_rejected_clocks, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that EINVAL is returned for unsupported clocks when "
+ "the lock cannot be granted");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+
+ATF_TC_BODY(clocklock_rejected_clocks, tc)
+{
+ check_rejected_clocks(false);
+ check_rejected_clocks(true);
+}
+
+ATF_TC(clocklock_invalid_abstime);
+ATF_TC_HEAD(clocklock_invalid_abstime, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that EINVAL is returned for an invalid timeout when "
+ "the lock cannot be granted");
+}
+
+ATF_TC_BODY(clocklock_invalid_abstime, tc)
+{
+ check_invalid_abstime(false);
+ check_invalid_abstime(true);
+}
+
+ATF_TC(clocklock_zero_deadline);
+ATF_TC_HEAD(clocklock_zero_deadline, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that the absolute time zero is treated as expired");
+ atf_tc_set_md_var(tc, "timeout", "30");
+}
+
+ATF_TC_BODY(clocklock_zero_deadline, tc)
+{
+ check_zero_deadline(false);
+ check_zero_deadline(true);
+}
+
+ATF_TC(clocklock_self_deadlock);
+ATF_TC_HEAD(clocklock_self_deadlock, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Checks that locking an rwlock write-locked by the calling "
+ "thread fails with EDEADLK without waiting");
+}
+
+ATF_TC_BODY(clocklock_self_deadlock, tc)
+{
+ pthread_rwlock_t rw;
+ struct timespec ts;
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_init(&rw, NULL));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_wrlock(&rw));
+ deadline(CLOCK_MONOTONIC, 60 * 1000, &ts);
+ ATF_CHECK_INTEQ(EDEADLK, pthread_rwlock_clockrdlock(&rw,
+ CLOCK_MONOTONIC, &ts));
+ ATF_CHECK_INTEQ(EDEADLK, pthread_rwlock_clockwrlock(&rw,
+ CLOCK_MONOTONIC, &ts));
+ ATF_CHECK_MSG(!clock_reached(CLOCK_MONOTONIC, &ts),
+ "waited for the timeout");
+
+ /* The calls do not block, so the arguments are not validated. */
+ ATF_CHECK_INTEQ(EDEADLK, pthread_rwlock_clockrdlock(&rw,
+ CLOCK_THREAD_CPUTIME_ID, &ts));
+ ATF_CHECK_INTEQ(EDEADLK, pthread_rwlock_clockwrlock(&rw,
+ CLOCK_THREAD_CPUTIME_ID, &ts));
+ ts.tv_nsec = 1000000000;
+ ATF_CHECK_INTEQ(EDEADLK, pthread_rwlock_clockrdlock(&rw,
+ CLOCK_MONOTONIC, &ts));
+ ATF_CHECK_INTEQ(EDEADLK, pthread_rwlock_clockwrlock(&rw,
+ CLOCK_MONOTONIC, &ts));
+
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_unlock(&rw));
+ ATF_REQUIRE_INTEQ(0, pthread_rwlock_destroy(&rw));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, clockrdlock_uncontended);
+ ATF_TP_ADD_TC(tp, clockwrlock_uncontended);
+ ATF_TP_ADD_TC(tp, clockrdlock_timeout_realtime);
+ ATF_TP_ADD_TC(tp, clockrdlock_timeout_monotonic);
+ ATF_TP_ADD_TC(tp, clockwrlock_timeout_realtime);
+ ATF_TP_ADD_TC(tp, clockwrlock_timeout_monotonic);
+ ATF_TP_ADD_TC(tp, clockrdlock_wakeup);
+ ATF_TP_ADD_TC(tp, clockwrlock_wakeup);
+ ATF_TP_ADD_TC(tp, clocklock_accepted_clocks);
+ ATF_TP_ADD_TC(tp, clocklock_rejected_clocks);
+ ATF_TP_ADD_TC(tp, clocklock_invalid_abstime);
+ ATF_TP_ADD_TC(tp, clocklock_zero_deadline);
+ ATF_TP_ADD_TC(tp, clocklock_self_deadlock);
+ return (atf_no_error());
+}
diff --git a/lib/libthr/thread/thr_cond.c b/lib/libthr/thread/thr_cond.c
--- a/lib/libthr/thread/thr_cond.c
+++ b/lib/libthr/thread/thr_cond.c
@@ -347,7 +347,7 @@
_pthread_exit(PTHREAD_CANCELED);
else /* this should not happen */
return (error2);
- } else if (error == ETIMEDOUT) {
+ } else if (error != 0 && error != EINTR) {
sq = _sleepq_lookup(cvp);
cvp->__has_user_waiters =
_sleepq_remove(sq, curthread);
@@ -442,6 +442,8 @@
if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
abstime->tv_nsec >= 1000000000)
return (EINVAL);
+ if (_thr_check_clockid(clockid) != 0)
+ return (EINVAL);
return (cond_wait_common(cond, mutex, clockid, abstime, true, true));
}
diff --git a/lib/libthr/thread/thr_mutex.c b/lib/libthr/thread/thr_mutex.c
--- a/lib/libthr/thread/thr_mutex.c
+++ b/lib/libthr/thread/thr_mutex.c
@@ -67,6 +67,8 @@
*/
int __pthread_mutex_timedlock(pthread_mutex_t * __restrict mutex,
const struct timespec * __restrict abstime);
+int __pthread_mutex_clocklock(pthread_mutex_t * __restrict mutex,
+ clockid_t clock, const struct timespec * __restrict abstime);
int _pthread_mutex_getspinloops_np(pthread_mutex_t *mutex, int *count);
int _pthread_mutex_setspinloops_np(pthread_mutex_t *mutex, int count);
int __pthread_mutex_setspinloops_np(pthread_mutex_t *mutex, int count);
@@ -76,10 +78,10 @@
static int mutex_self_trylock(pthread_mutex_t);
static int mutex_self_lock(pthread_mutex_t,
- const struct timespec *abstime);
+ const struct timespec *abstime, clockid_t clockid);
static int mutex_unlock_common(struct pthread_mutex *, bool, int *);
static int mutex_lock_sleep(struct pthread *, pthread_mutex_t,
- const struct timespec *);
+ const struct timespec *, clockid_t clockid);
static void mutex_init_robust(struct pthread *curthread);
static int mutex_qidx(struct pthread_mutex *m);
static bool is_robust_mutex(struct pthread_mutex *m);
@@ -93,6 +95,7 @@
__strong_reference(__Tthr_mutex_lock, _pthread_mutex_lock);
__weak_reference(__pthread_mutex_timedlock, pthread_mutex_timedlock);
__strong_reference(__pthread_mutex_timedlock, _pthread_mutex_timedlock);
+__weak_reference(__pthread_mutex_clocklock, pthread_mutex_clocklock);
__weak_reference(__Tthr_mutex_trylock, pthread_mutex_trylock);
__weak_reference(__Tthr_mutex_trylock, __pthread_mutex_trylock);
__strong_reference(__Tthr_mutex_trylock, _pthread_mutex_trylock);
@@ -656,14 +659,14 @@
static int
mutex_lock_sleep(struct pthread *curthread, struct pthread_mutex *m,
- const struct timespec *abstime)
+ const struct timespec *abstime, clockid_t clockid)
{
uint32_t id, owner;
int count, ret;
id = TID(curthread);
if (PMUTEX_OWNER_ID(m) == id)
- return (mutex_self_lock(m, abstime));
+ return (mutex_self_lock(m, abstime, clockid));
/*
* For adaptive mutexes, spin for a bit in the expectation
@@ -709,10 +712,11 @@
if (abstime == NULL)
ret = __thr_umutex_lock(&m->m_lock, id);
else if (__predict_false(abstime->tv_nsec < 0 ||
- abstime->tv_nsec >= 1000000000))
+ abstime->tv_nsec >= 1000000000) ||
+ _thr_check_clockid(clockid) != 0)
ret = EINVAL;
else
- ret = __thr_umutex_timedlock(&m->m_lock, id, abstime);
+ ret = __thr_umutex_timedlock(&m->m_lock, id, abstime, clockid);
done:
if (ret == 0 || ret == EOWNERDEAD) {
enqueue_mutex(curthread, m, ret);
@@ -724,7 +728,7 @@
static __always_inline int
mutex_lock_common(struct pthread_mutex *m, const struct timespec *abstime,
- bool cvattach, bool rb_onlist)
+ clockid_t clockid, bool cvattach, bool rb_onlist)
{
struct pthread *curthread;
int ret, robust;
@@ -741,7 +745,7 @@
if (ret == EOWNERDEAD)
m->m_lock.m_flags |= UMUTEX_NONCONSISTENT;
} else {
- ret = mutex_lock_sleep(curthread, m, abstime);
+ ret = mutex_lock_sleep(curthread, m, abstime, clockid);
}
if (!rb_onlist && robust)
_mutex_leave_robust(curthread, m);
@@ -760,12 +764,12 @@
_thr_check_init();
ret = check_and_init_mutex(mutex, &m);
if (ret == 0)
- ret = mutex_lock_common(m, NULL, false, false);
+ ret = mutex_lock_common(m, NULL, CLOCK_REALTIME, false, false);
return (ret);
}
int
-__pthread_mutex_timedlock(pthread_mutex_t * __restrict mutex,
+__pthread_mutex_clocklock(pthread_mutex_t * __restrict mutex, clockid_t clockid,
const struct timespec * __restrict abstime)
{
struct pthread_mutex *m;
@@ -774,10 +778,17 @@
_thr_check_init();
ret = check_and_init_mutex(mutex, &m);
if (ret == 0)
- ret = mutex_lock_common(m, abstime, false, false);
+ ret = mutex_lock_common(m, abstime, clockid, false, false);
return (ret);
}
+int
+__pthread_mutex_timedlock(pthread_mutex_t * __restrict mutex,
+ const struct timespec * __restrict abstime)
+{
+ return (__pthread_mutex_clocklock(mutex, CLOCK_REALTIME, abstime));
+}
+
int
_thr_mutex_unlock(pthread_mutex_t *mutex)
{
@@ -799,7 +810,7 @@
{
int error;
- error = mutex_lock_common(m, NULL, true, rb_onlist);
+ error = mutex_lock_common(m, NULL, CLOCK_REALTIME, true, rb_onlist);
if (error == 0 || error == EOWNERDEAD)
m->m_count = count;
return (error);
@@ -891,7 +902,8 @@
}
static int
-mutex_self_lock(struct pthread_mutex *m, const struct timespec *abstime)
+mutex_self_lock(struct pthread_mutex *m, const struct timespec *abstime,
+ clockid_t clockid)
{
struct timespec ts1, ts2;
int ret;
@@ -899,23 +911,11 @@
switch (PMUTEX_TYPE(m->m_flags)) {
case PTHREAD_MUTEX_ERRORCHECK:
case PTHREAD_MUTEX_ADAPTIVE_NP:
- if (abstime) {
- if (abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
- abstime->tv_nsec >= 1000000000) {
- ret = EINVAL;
- } else {
- clock_gettime(CLOCK_REALTIME, &ts1);
- TIMESPEC_SUB(&ts2, abstime, &ts1);
- __sys_nanosleep(&ts2, NULL);
- ret = ETIMEDOUT;
- }
- } else {
- /*
- * POSIX specifies that mutexes should return
- * EDEADLK if a recursive lock is detected.
- */
- ret = EDEADLK;
- }
+ /*
+ * POSIX specifies that mutexes should return EDEADLK
+ * if a recursive lock is detected.
+ */
+ ret = EDEADLK;
break;
case PTHREAD_MUTEX_NORMAL:
@@ -929,10 +929,14 @@
abstime->tv_nsec >= 1000000000) {
ret = EINVAL;
} else {
- clock_gettime(CLOCK_REALTIME, &ts1);
- TIMESPEC_SUB(&ts2, abstime, &ts1);
- __sys_nanosleep(&ts2, NULL);
- ret = ETIMEDOUT;
+ if (_thr_check_clockid(clockid) != 0 ||
+ clock_gettime(clockid, &ts1) == -1) {
+ ret = EINVAL;
+ } else {
+ TIMESPEC_SUB(&ts2, abstime, &ts1);
+ __sys_nanosleep(&ts2, NULL);
+ ret = ETIMEDOUT;
+ }
}
} else {
ts1.tv_sec = 30;
diff --git a/lib/libthr/thread/thr_private.h b/lib/libthr/thread/thr_private.h
--- a/lib/libthr/thread/thr_private.h
+++ b/lib/libthr/thread/thr_private.h
@@ -996,6 +996,34 @@
return TAILQ_FIRST(&sq->sq_blocked);
}
+/*
+ * Must accept the same clocks as umtx_check_clockid() in
+ * sys/kern/kern_umtx.c.
+ */
+static inline int
+_thr_check_clockid(clockid_t clockid)
+{
+ switch (clockid) {
+ case CLOCK_REALTIME:
+ case CLOCK_REALTIME_PRECISE:
+ case CLOCK_REALTIME_FAST:
+ case CLOCK_MONOTONIC:
+ case CLOCK_MONOTONIC_PRECISE:
+ case CLOCK_MONOTONIC_FAST:
+ case CLOCK_UPTIME:
+ case CLOCK_UPTIME_PRECISE:
+ case CLOCK_UPTIME_FAST:
+ case CLOCK_SECOND:
+ case CLOCK_VIRTUAL:
+ case CLOCK_PROF:
+ case CLOCK_PROCESS_CPUTIME_ID:
+ case CLOCK_TAI:
+ return (0);
+ default:
+ return (EINVAL);
+ }
+}
+
void _sleepq_init(void) __hidden;
struct sleepqueue *_sleepq_alloc(void) __hidden;
void _sleepq_free(struct sleepqueue *) __hidden;
diff --git a/lib/libthr/thread/thr_rwlock.c b/lib/libthr/thread/thr_rwlock.c
--- a/lib/libthr/thread/thr_rwlock.c
+++ b/lib/libthr/thread/thr_rwlock.c
@@ -39,6 +39,11 @@
_Static_assert(sizeof(struct pthread_rwlock) <= THR_PAGE_SIZE_MIN,
"pthread_rwlock is too large for off-page");
+int _pthread_rwlock_clockrdlock(pthread_rwlock_t * __restrict rwlock,
+ clockid_t clockid, const struct timespec * __restrict abstime);
+int _pthread_rwlock_clockwrlock(pthread_rwlock_t * __restrict rwlock,
+ clockid_t clockid, const struct timespec * __restrict abstime);
+
__weak_reference(_thr_rwlock_destroy, pthread_rwlock_destroy);
__weak_reference(_thr_rwlock_destroy, _pthread_rwlock_destroy);
__weak_reference(_thr_rwlock_init, pthread_rwlock_init);
@@ -46,6 +51,7 @@
__weak_reference(_Tthr_rwlock_rdlock, pthread_rwlock_rdlock);
__weak_reference(_Tthr_rwlock_rdlock, _pthread_rwlock_rdlock);
__weak_reference(_pthread_rwlock_timedrdlock, pthread_rwlock_timedrdlock);
+__weak_reference(_pthread_rwlock_clockrdlock, pthread_rwlock_clockrdlock);
__weak_reference(_Tthr_rwlock_tryrdlock, pthread_rwlock_tryrdlock);
__weak_reference(_Tthr_rwlock_tryrdlock, _pthread_rwlock_tryrdlock);
__weak_reference(_Tthr_rwlock_trywrlock, pthread_rwlock_trywrlock);
@@ -55,6 +61,7 @@
__weak_reference(_Tthr_rwlock_wrlock, pthread_rwlock_wrlock);
__weak_reference(_Tthr_rwlock_wrlock, _pthread_rwlock_wrlock);
__weak_reference(_pthread_rwlock_timedwrlock, pthread_rwlock_timedwrlock);
+__weak_reference(_pthread_rwlock_clockwrlock, pthread_rwlock_clockwrlock);
static int init_static(struct pthread *thread, pthread_rwlock_t *rwlock);
static int init_rwlock(pthread_rwlock_t *rwlock, pthread_rwlock_t *rwlock_out);
@@ -194,7 +201,8 @@
}
static int
-rwlock_rdlock_common(pthread_rwlock_t *rwlock, const struct timespec *abstime)
+rwlock_rdlock_common(pthread_rwlock_t *rwlock, const struct timespec *abstime,
+ clockid_t clockid)
{
struct pthread *curthread = _get_curthread();
pthread_rwlock_t prwlock;
@@ -225,7 +233,8 @@
for (;;) {
/* goto kernel and lock it */
- ret = __thr_rwlock_rdlock(&prwlock->lock, flags, abstime);
+ ret = __thr_rwlock_rdlock(&prwlock->lock, flags, abstime,
+ clockid);
if (ret != EINTR)
break;
@@ -244,7 +253,7 @@
_Tthr_rwlock_rdlock(pthread_rwlock_t *rwlock)
{
_thr_check_init();
- return (rwlock_rdlock_common(rwlock, NULL));
+ return (rwlock_rdlock_common(rwlock, NULL, CLOCK_REALTIME));
}
int
@@ -252,7 +261,15 @@
const struct timespec * __restrict abstime)
{
_thr_check_init();
- return (rwlock_rdlock_common(rwlock, abstime));
+ return (rwlock_rdlock_common(rwlock, abstime, CLOCK_REALTIME));
+}
+
+int
+_pthread_rwlock_clockrdlock(pthread_rwlock_t * __restrict rwlock,
+ clockid_t clockid, const struct timespec * __restrict abstime)
+{
+ _thr_check_init();
+ return (rwlock_rdlock_common(rwlock, abstime, clockid));
}
int
@@ -294,7 +311,8 @@
}
static int
-rwlock_wrlock_common(pthread_rwlock_t *rwlock, const struct timespec *abstime)
+rwlock_wrlock_common(pthread_rwlock_t *rwlock, const struct timespec *abstime,
+ clockid_t clockid)
{
struct pthread *curthread = _get_curthread();
pthread_rwlock_t prwlock;
@@ -322,7 +340,7 @@
for (;;) {
/* goto kernel and lock it */
- ret = __thr_rwlock_wrlock(&prwlock->lock, abstime);
+ ret = __thr_rwlock_wrlock(&prwlock->lock, abstime, clockid);
if (ret == 0) {
prwlock->owner = TID(curthread);
break;
@@ -345,7 +363,7 @@
_Tthr_rwlock_wrlock(pthread_rwlock_t *rwlock)
{
_thr_check_init();
- return (rwlock_wrlock_common(rwlock, NULL));
+ return (rwlock_wrlock_common(rwlock, NULL, CLOCK_REALTIME));
}
int
@@ -353,7 +371,15 @@
const struct timespec * __restrict abstime)
{
_thr_check_init();
- return (rwlock_wrlock_common(rwlock, abstime));
+ return (rwlock_wrlock_common(rwlock, abstime, CLOCK_REALTIME));
+}
+
+int
+_pthread_rwlock_clockwrlock(pthread_rwlock_t * __restrict rwlock,
+ clockid_t clockid, const struct timespec * __restrict abstime)
+{
+ _thr_check_init();
+ return (rwlock_wrlock_common(rwlock, abstime, clockid));
}
int
diff --git a/lib/libthr/thread/thr_umtx.h b/lib/libthr/thread/thr_umtx.h
--- a/lib/libthr/thread/thr_umtx.h
+++ b/lib/libthr/thread/thr_umtx.h
@@ -42,7 +42,7 @@
int __thr_umutex_lock(struct umutex *mtx, uint32_t id) __hidden;
int __thr_umutex_lock_spin(struct umutex *mtx, uint32_t id) __hidden;
int __thr_umutex_timedlock(struct umutex *mtx, uint32_t id,
- const struct timespec *timeout) __hidden;
+ const struct timespec *timeout, clockid_t clockid) __hidden;
int __thr_umutex_unlock(struct umutex *mtx) __hidden;
int __thr_umutex_trylock(struct umutex *mtx) __hidden;
int __thr_umutex_set_ceiling(struct umutex *mtx, uint32_t ceiling,
@@ -66,9 +66,9 @@
int _thr_ucond_broadcast(struct ucond *cv) __hidden;
int __thr_rwlock_rdlock(struct urwlock *rwlock, int flags,
- const struct timespec *tsp) __hidden;
+ const struct timespec *tsp, clockid_t clockid) __hidden;
int __thr_rwlock_wrlock(struct urwlock *rwlock,
- const struct timespec *tsp) __hidden;
+ const struct timespec *tsp, clockid_t clockid) __hidden;
int __thr_rwlock_unlock(struct urwlock *rwlock) __hidden;
/* Internal used only */
@@ -145,7 +145,7 @@
if (_thr_umutex_trylock2(mtx, id) == 0)
return (0);
- return (__thr_umutex_timedlock(mtx, id, timeout));
+ return (__thr_umutex_timedlock(mtx, id, timeout, CLOCK_REALTIME));
}
static inline int
@@ -233,7 +233,7 @@
if (_thr_rwlock_tryrdlock(rwlock, flags) == 0)
return (0);
- return (__thr_rwlock_rdlock(rwlock, flags, tsp));
+ return (__thr_rwlock_rdlock(rwlock, flags, tsp, CLOCK_REALTIME));
}
static inline int
@@ -242,7 +242,7 @@
if (_thr_rwlock_trywrlock(rwlock) == 0)
return (0);
- return (__thr_rwlock_wrlock(rwlock, tsp));
+ return (__thr_rwlock_wrlock(rwlock, tsp, CLOCK_REALTIME));
}
static inline int
diff --git a/lib/libthr/thread/thr_umtx.c b/lib/libthr/thread/thr_umtx.c
--- a/lib/libthr/thread/thr_umtx.c
+++ b/lib/libthr/thread/thr_umtx.c
@@ -108,7 +108,7 @@
int
__thr_umutex_timedlock(struct umutex *mtx, uint32_t id,
- const struct timespec *abstime)
+ const struct timespec *abstime, clockid_t clockid)
{
struct _umtx_time *tm_p, timeout;
size_t tm_size;
@@ -119,7 +119,7 @@
tm_p = NULL;
tm_size = 0;
} else {
- timeout._clockid = CLOCK_REALTIME;
+ timeout._clockid = clockid;
timeout._flags = UMTX_ABSTIME;
timeout._timeout = *abstime;
tm_p = &timeout;
@@ -151,7 +151,7 @@
ret == ENOTRECOVERABLE)
break;
}
- if (ret == ETIMEDOUT)
+ if (ret == ETIMEDOUT || ret == EINVAL)
break;
}
return (ret);
@@ -292,7 +292,7 @@
int
__thr_rwlock_rdlock(struct urwlock *rwlock, int flags,
- const struct timespec *tsp)
+ const struct timespec *tsp, clockid_t clockid)
{
struct _umtx_time timeout, *tm_p;
size_t tm_size;
@@ -303,7 +303,7 @@
} else {
timeout._timeout = *tsp;
timeout._flags = UMTX_ABSTIME;
- timeout._clockid = CLOCK_REALTIME;
+ timeout._clockid = clockid;
tm_p = &timeout;
tm_size = sizeof(timeout);
}
@@ -312,7 +312,8 @@
}
int
-__thr_rwlock_wrlock(struct urwlock *rwlock, const struct timespec *tsp)
+__thr_rwlock_wrlock(struct urwlock *rwlock, const struct timespec *tsp,
+ clockid_t clockid)
{
struct _umtx_time timeout, *tm_p;
size_t tm_size;
@@ -323,7 +324,7 @@
} else {
timeout._timeout = *tsp;
timeout._flags = UMTX_ABSTIME;
- timeout._clockid = CLOCK_REALTIME;
+ timeout._clockid = clockid;
tm_p = &timeout;
tm_size = sizeof(timeout);
}
@@ -346,7 +347,8 @@
for (;;) {
if (_thr_rwlock_tryrdlock(rwlock, URWLOCK_PREFER_READER) == 0)
return;
- ret = __thr_rwlock_rdlock(rwlock, URWLOCK_PREFER_READER, NULL);
+ ret = __thr_rwlock_rdlock(rwlock, URWLOCK_PREFER_READER, NULL,
+ CLOCK_REALTIME);
if (ret == 0)
return;
if (ret != EINTR)
@@ -362,7 +364,7 @@
for (;;) {
if (_thr_rwlock_trywrlock(rwlock) == 0)
return;
- ret = __thr_rwlock_wrlock(rwlock, NULL);
+ ret = __thr_rwlock_wrlock(rwlock, NULL, CLOCK_REALTIME);
if (ret == 0)
return;
if (ret != EINTR)
diff --git a/share/man/man3/Makefile b/share/man/man3/Makefile
--- a/share/man/man3/Makefile
+++ b/share/man/man3/Makefile
@@ -514,6 +514,7 @@
PTHREAD_MLINKS+=pthread_cond_timedwait.3 pthread_cond_clockwait.3
PTHREAD_MLINKS+=pthread_getconcurrency.3 pthread_setconcurrency.3
PTHREAD_MLINKS+=pthread_multi_np.3 pthread_single_np.3
+PTHREAD_MLINKS+=pthread_mutex_timedlock.3 pthread_mutex_clocklock.3
PTHREAD_MLINKS+=pthread_mutexattr.3 pthread_mutexattr_destroy.3 \
pthread_mutexattr.3 pthread_mutexattr_getprioceiling.3 \
pthread_mutexattr.3 pthread_mutexattr_getprotocol.3 \
@@ -529,6 +530,8 @@
PTHREAD_MLINKS+=pthread_mutexattr_getkind_np.3 pthread_mutexattr_setkind_np.3
PTHREAD_MLINKS+=pthread_rwlock_rdlock.3 pthread_rwlock_tryrdlock.3
PTHREAD_MLINKS+=pthread_rwlock_wrlock.3 pthread_rwlock_trywrlock.3
+PTHREAD_MLINKS+=pthread_rwlock_timedwrlock.3 pthread_rwlock_clockwrlock.3
+PTHREAD_MLINKS+=pthread_rwlock_timedrdlock.3 pthread_rwlock_clockrdlock.3
PTHREAD_MLINKS+=pthread_schedparam.3 pthread_getschedparam.3 \
pthread_schedparam.3 pthread_setschedparam.3
PTHREAD_MLINKS+=pthread_set_name_np.3 pthread_get_name_np.3 \
diff --git a/share/man/man3/pthread_cond_timedwait.3 b/share/man/man3/pthread_cond_timedwait.3
--- a/share/man/man3/pthread_cond_timedwait.3
+++ b/share/man/man3/pthread_cond_timedwait.3
@@ -29,7 +29,8 @@
.Dt PTHREAD_COND_TIMEDWAIT 3
.Os
.Sh NAME
-.Nm pthread_cond_timedwait
+.Nm pthread_cond_timedwait ,
+.Nm pthread_cond_clockwait
.Nd "wait on a condition variable for a specific amount of time"
.Sh LIBRARY
.Lb libpthread
@@ -126,6 +127,9 @@
The
.Fn pthread_cond_clockwait
function conforms to
-.St -p1003.1-2024
-and appeared in
+.St -p1003.1-2024 .
+.Sh HISTORY
+The
+.Fn pthread_cond_clockwait
+function first appeared in
.Fx 16.0 .
diff --git a/share/man/man3/pthread_mutex_timedlock.3 b/share/man/man3/pthread_mutex_timedlock.3
--- a/share/man/man3/pthread_mutex_timedlock.3
+++ b/share/man/man3/pthread_mutex_timedlock.3
@@ -26,6 +26,7 @@
.Dt PTHREAD_MUTEX_TIMEDLOCK 3
.Os
.Sh NAME
+.Nm pthread_mutex_clocklock ,
.Nm pthread_mutex_timedlock
.Nd lock a mutex without blocking indefinitely
.Sh LIBRARY
@@ -34,30 +35,58 @@
.In pthread.h
.In time.h
.Ft int
-.Fn pthread_mutex_timedlock "pthread_mutex_t *restrict mutex" "const struct timespec *restrict abs_timeout"
+.Fo pthread_mutex_clocklock
+.Fa "pthread_mutex_t *restrict mutex"
+.Fa "clockid_t clockid"
+.Fa "const struct timespec *restrict abs_timeout"
+.Fc
+.Ft int
+.Fo pthread_mutex_timedlock
+.Fa "pthread_mutex_t *restrict mutex"
+.Fa "const struct timespec *restrict abs_timeout"
+.Fc
.Sh DESCRIPTION
The
-.Fn pthread_mutex_timedlock
-function will lock
+.Fn pthread_mutex_clocklock
+function locks
.Fa mutex .
-If it is already locked the calling thread will block until
-the mutex becomes available or
-the timeout,
-specified by abs_timeout,
-expires.
-The time of the timeout is an absolute time and
-is not relative to the current time.
+If the mutex is already locked, the calling thread blocks until the
+mutex becomes available or the timeout expires.
+The timeout expires when the clock specified by
+.Fa clockid
+reaches or passes the absolute time
+.Fa abs_timeout ;
+it is not relative to the current time.
+If the mutex can be locked immediately, the call does not fail
+with a timeout, and
+.Fa abs_timeout
+and
+.Fa clockid
+are not checked for validity.
+.Pp
+The
+.Fn pthread_mutex_timedlock
+function is equivalent to
+.Fn pthread_mutex_clocklock
+with
+.Fa clockid
+set to
+.Dv CLOCK_REALTIME .
.Sh RETURN VALUES
If successful,
+.Fn pthread_mutex_clocklock
+and
.Fn pthread_mutex_timedlock
will return zero, otherwise an error number will be returned to
indicate the error.
.Sh ERRORS
The
+.Fn pthread_mutex_clocklock
+and
.Fn pthread_mutex_timedlock
-function will fail if:
+functions will fail if:
.Bl -tag -width Er
-.It Bq "Er ENOTRECOVERABLE"
+.It Bq Er ENOTRECOVERABLE
The
.Fa mutex
was created with the protocol attribute having the
@@ -73,6 +102,17 @@
The
.Fa mutex
parameter is invalid.
+.It Bq Er EINVAL
+The
+.Fa clockid
+parameter is invalid.
+The
+.Dv CLOCK_REALTIME
+and
+.Dv CLOCK_MONOTONIC
+clocks are always supported; see
+.Xr _umtx_op 2
+for the full list of accepted clocks.
.It Bq Er ETIMEDOUT
The
.Fa mutex
@@ -85,7 +125,7 @@
.Fa mutex
has been exceeded.
.It Bq Er EDEADLK
-The current thread already owns the
+The current thread already owns the error-checking
.Fa mutex .
.It Bq Er EOWNERDEAD
The argument
@@ -109,5 +149,14 @@
.Sh STANDARDS
The
.Fn pthread_mutex_timedlock
-function is expected to conform to
+function conforms to
.St -p1003.1-96 .
+The
+.Fn pthread_mutex_clocklock
+function conforms to
+.St -p1003.1-2024 .
+.Sh HISTORY
+The
+.Fn pthread_mutex_clocklock
+function first appeared in
+.Fx 16.0 .
diff --git a/share/man/man3/pthread_rwlock_timedrdlock.3 b/share/man/man3/pthread_rwlock_timedrdlock.3
--- a/share/man/man3/pthread_rwlock_timedrdlock.3
+++ b/share/man/man3/pthread_rwlock_timedrdlock.3
@@ -26,6 +26,7 @@
.Dt PTHREAD_RWLOCK_TIMEDRDLOCK 3
.Os
.Sh NAME
+.Nm pthread_rwlock_clockrdlock ,
.Nm pthread_rwlock_timedrdlock
.Nd "acquire a read-write lock for reading or give up after a specified period"
.Sh LIBRARY
@@ -33,16 +34,32 @@
.Sh SYNOPSIS
.In pthread.h
.Ft int
-.Fn pthread_rwlock_timedrdlock "pthread_rwlock_t *restrict rwlock" "const struct timespec *restrict abs_timeout"
+.Fo pthread_rwlock_clockrdlock
+.Fa "pthread_rwlock_t *restrict rwlock"
+.Fa "clockid_t clockid"
+.Fa "const struct timespec *restrict abs_timeout"
+.Fc
+.Ft int
+.Fo pthread_rwlock_timedrdlock
+.Fa "pthread_rwlock_t *restrict rwlock"
+.Fa "const struct timespec *restrict abs_timeout"
+.Fc
.Sh DESCRIPTION
-This function acquires a read lock on the read-write lock
+These functions acquire a read lock on the read-write lock
.Fa rwlock .
However, if the lock cannot be
acquired without waiting for another thread to
unlock the lock,
-this wait shall be terminated when
+this wait is terminated when
.Fa abs_timeout
expires.
+The
+.Fa abs_timeout
+value is an absolute time, measured on
+.Fa clockid
+for the clock variant and on
+.Dv CLOCK_REALTIME
+for the timed variant.
.Pp
A thread may hold multiple concurrent read locks.
The
@@ -50,38 +67,30 @@
function must be called once for each lock acquired.
.Pp
If the thread should be interrupted by a signal,
-the
-.Fn pthread_rwlock_timedrdlock
-function will be automatically restarted after the thread returns from
-the signal handler.
+the functions are automatically restarted after the thread
+returns from the signal handler.
.Pp
The calling thread may deadlock if
at the time the call is made it holds a write lock on
.Fa rwlock .
-The results are undefined if this function is called with
+The results are undefined if these functions are called with
an uninitialized read-write lock.
.Sh IMPLEMENTATION NOTES
To prevent writer starvation, writers are favored over readers.
.Sh RETURN VALUES
-If successful, the
-.Fn pthread_rwlock_timedrdlock
-function will return zero.
+If successful, the functions will return zero.
Otherwise, an error number will be returned to indicate the error.
.Pp
-This function shall not return an error code of
+These functions do not return an error code of
.Er EINTR .
.Sh ERRORS
-The
-.Fn pthread_rwlock_timedrdlock
-function will fail if:
+The functions will fail if:
.Bl -tag -width Er
.It Bq Er ETIMEDOUT
The lock could not be acquired before the specified timeout expired.
.El
.Pp
-The
-.Fn pthread_rwlock_timedrdlock
-function may fail if:
+The functions may fail if:
.Bl -tag -width Er
.It Bq Er EAGAIN
The read lock could not be
@@ -98,7 +107,17 @@
or the
.Fa abs_timeout
nanosecond value is less than zero or
-greater than or equal to 1 billion.
+greater than or equal to 1 billion, or the
+.Fa clockid
+parameter is invalid.
+.Pp
+The
+.Dv CLOCK_REALTIME
+and
+.Dv CLOCK_MONOTONIC
+clocks are always supported; see
+.Xr _umtx_op 2
+for the full list of accepted clocks.
.El
.Sh SEE ALSO
.Xr pthread_rwlock_init 3 ,
@@ -107,10 +126,18 @@
.Sh STANDARDS
The
.Fn pthread_rwlock_timedrdlock
-function is expected to conform to
+function conforms to
.St -p1003.1-96 .
+The
+.Fn pthread_rwlock_clockrdlock
+function conforms to
+.St -p1003.1-2024 .
.Sh HISTORY
The
.Fn pthread_rwlock_timedrdlock
function first appeared in
.Fx 5.2 .
+The
+.Fn pthread_rwlock_clockrdlock
+function first appeared in
+.Fx 16.0 .
diff --git a/share/man/man3/pthread_rwlock_timedwrlock.3 b/share/man/man3/pthread_rwlock_timedwrlock.3
--- a/share/man/man3/pthread_rwlock_timedwrlock.3
+++ b/share/man/man3/pthread_rwlock_timedwrlock.3
@@ -26,6 +26,7 @@
.Dt PTHREAD_RWLOCK_TIMEDWRLOCK 3
.Os
.Sh NAME
+.Nm pthread_rwlock_clockwrlock ,
.Nm pthread_rwlock_timedwrlock
.Nd "acquire a read-write lock for writing or give up after a specified period"
.Sh LIBRARY
@@ -33,50 +34,58 @@
.Sh SYNOPSIS
.In pthread.h
.Ft int
-.Fn pthread_rwlock_timedwrlock "pthread_rwlock_t *restrict rwlock" "const struct timespec *restrict abs_timeout"
+.Fo pthread_rwlock_clockwrlock
+.Fa "pthread_rwlock_t *restrict rwlock"
+.Fa "clockid_t clockid"
+.Fa "const struct timespec *restrict abs_timeout"
+.Fc
+.Ft int
+.Fo pthread_rwlock_timedwrlock
+.Fa "pthread_rwlock_t *restrict rwlock"
+.Fa "const struct timespec *restrict abs_timeout"
+.Fc
.Sh DESCRIPTION
-This function acquires a write lock on the read-write lock
+These functions acquire a write lock on the read-write lock
.Fa rwlock .
However, if the lock cannot be
acquired without waiting for another thread to
unlock the lock,
-this wait shall be terminated when
+this wait is terminated when
.Fa abs_timeout
expires.
+The
+.Fa abs_timeout
+value is an absolute time, measured on
+.Fa clockid
+for the clock variant and on
+.Dv CLOCK_REALTIME
+for the timed variant.
.Pp
If the thread should be interrupted by a signal,
-the
-.Fn pthread_rwlock_timedwrlock
-function will be automatically restarted after the thread returns from
-the signal handler.
+the functions are automatically restarted after the thread
+returns from the signal handler.
.Pp
The calling thread may deadlock if
at the time the call is made it holds
.Fa rwlock .
-The results are undefined if this function is called with
+The results are undefined if these functions are called with
an uninitialized read-write lock.
.Sh IMPLEMENTATION NOTES
To prevent writer starvation, writers are favored over readers.
.Sh RETURN VALUES
-If successful, the
-.Fn pthread_rwlock_timedwrlock
-function will return zero.
+If successful, the functions will return zero.
Otherwise, an error number will be returned to indicate the error.
.Pp
-This function shall not return an error code of
+These functions do not return an error code of
.Er EINTR .
.Sh ERRORS
-The
-.Fn pthread_rwlock_timedwrlock
-function shall fail if:
+The functions will fail if:
.Bl -tag -width Er
.It Bq Er ETIMEDOUT
The lock could not be acquired before the specified timeout expired.
.El
.Pp
-The
-.Fn pthread_rwlock_timedwrlock
-function may fail if:
+The functions may fail if:
.Bl -tag -width Er
.It Bq Er EDEADLK
The calling thread already holds
@@ -88,7 +97,17 @@
or the
.Fa abs_timeout
nanosecond value is less than zero or
-greater than or equal to 1 billion.
+greater than or equal to 1 billion, or the
+.Fa clockid
+parameter is invalid.
+.Pp
+The
+.Dv CLOCK_REALTIME
+and
+.Dv CLOCK_MONOTONIC
+clocks are always supported; see
+.Xr _umtx_op 2
+for the full list of accepted clocks.
.El
.Sh SEE ALSO
.Xr pthread_rwlock_init 3 ,
@@ -97,10 +116,18 @@
.Sh STANDARDS
The
.Fn pthread_rwlock_timedwrlock
-function is expected to conform to
+function conforms to
.St -p1003.1-96 .
+The
+.Fn pthread_rwlock_clockwrlock
+function conforms to
+.St -p1003.1-2024 .
.Sh HISTORY
The
.Fn pthread_rwlock_timedwrlock
function first appeared in
.Fx 5.2 .
+The
+.Fn pthread_rwlock_clockwrlock
+function first appeared in
+.Fx 16.0 .
diff --git a/sys/kern/kern_umtx.c b/sys/kern/kern_umtx.c
--- a/sys/kern/kern_umtx.c
+++ b/sys/kern/kern_umtx.c
@@ -171,7 +171,7 @@
"umtx chain stats");
#endif
-static inline void umtx_abs_timeout_init2(struct umtx_abs_timeout *timo,
+static inline int umtx_abs_timeout_init2(struct umtx_abs_timeout *timo,
const struct _umtx_time *umtxtime);
static void umtx_shm_init(void);
@@ -678,15 +678,59 @@
return tvtohz(&tv);
}
-void
+/*
+ * Clocks that umtx_abs_timeout_getsbt() can wait on.
+ *
+ * The process CPU-time clocks CLOCK_VIRTUAL, CLOCK_PROF, and
+ * CLOCK_PROCESS_CPUTIME_ID advance while other threads of the process
+ * run. CLOCK_THREAD_CPUTIME_ID is not accepted because the CPU time
+ * of the sleeping thread does not advance.
+ *
+ * The clock ids from clock_getcpuclockid(3) and
+ * pthread_getcpuclockid(3) are not accepted, at least for now.
+ * Reading the CPU time of another process takes sleepable locks,
+ * while the timeout is checked with the umtx chain lock held.
+ */
+static int
+umtx_check_clockid(clockid_t clockid)
+{
+ switch (clockid) {
+ case CLOCK_REALTIME:
+ case CLOCK_REALTIME_PRECISE:
+ case CLOCK_REALTIME_FAST:
+ case CLOCK_MONOTONIC:
+ case CLOCK_MONOTONIC_PRECISE:
+ case CLOCK_MONOTONIC_FAST:
+ case CLOCK_UPTIME:
+ case CLOCK_UPTIME_PRECISE:
+ case CLOCK_UPTIME_FAST:
+ case CLOCK_SECOND:
+ case CLOCK_VIRTUAL:
+ case CLOCK_PROF:
+ case CLOCK_PROCESS_CPUTIME_ID:
+ case CLOCK_TAI:
+ return (0);
+ default:
+ return (EINVAL);
+ }
+}
+
+int
umtx_abs_timeout_init(struct umtx_abs_timeout *timo, int clockid,
int absolute, const struct timespec *timeout)
{
+ int error;
+ error = umtx_check_clockid(clockid);
+ if (error != 0)
+ return (error);
timo->clockid = clockid;
if (!absolute) {
timo->is_abs_real = false;
- kern_clock_gettime(curthread, timo->clockid, &timo->cur);
+ error = kern_clock_gettime(curthread, timo->clockid,
+ &timo->cur);
+ if (error != 0)
+ return (error);
timespecadd(&timo->cur, timeout, &timo->end);
} else {
timo->end = *timeout;
@@ -696,15 +740,16 @@
clockid == CLOCK_TAI ||
clockid == CLOCK_SECOND;
}
+ return (0);
}
-static void
+static int
umtx_abs_timeout_init2(struct umtx_abs_timeout *timo,
const struct _umtx_time *umtxtime)
{
- umtx_abs_timeout_init(timo, umtxtime->_clockid,
- (umtxtime->_flags & UMTX_ABSTIME) != 0, &umtxtime->_timeout);
+ return (umtx_abs_timeout_init(timo, umtxtime->_clockid,
+ (umtxtime->_flags & UMTX_ABSTIME) != 0, &umtxtime->_timeout));
}
static void
@@ -727,6 +772,7 @@
struct bintime bt, bbt;
struct timespec tts;
sbintime_t rem;
+ int error;
switch (timo->clockid) {
@@ -790,7 +836,10 @@
case CLOCK_PROCESS_CPUTIME_ID:
case CLOCK_TAI: /* Boot time is not necessarily stable in TAI */
default:
- kern_clock_gettime(curthread, timo->clockid, &timo->cur);
+ error = kern_clock_gettime(curthread, timo->clockid,
+ &timo->cur);
+ if (error != 0)
+ return (error);
if (timespeccmp(&timo->end, &timo->cur, <=))
return (ETIMEDOUT);
timespecsub(&timo->end, &timo->cur, &tts);
@@ -839,6 +888,15 @@
error = umtx_abs_timeout_getsbt(timo, &sbt, &flags);
if (error != 0)
break;
+
+ /*
+ * msleep() treats zero timeout as no timeout,
+ * return ETIMEDOUT right away.
+ */
+ if ((flags & C_ABSOLUTE) != 0 && sbt == 0) {
+ error = ETIMEDOUT;
+ break;
+ }
}
error = msleep_sbt(uq, &uc->uc_lock, PCATCH | PDROP, wmesg,
sbt, 0, flags);
@@ -927,8 +985,12 @@
int error = 0;
uq = td->td_umtxq;
- if (timeout != NULL)
- umtx_abs_timeout_init(&timo, CLOCK_REALTIME, 0, timeout);
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init(&timo, CLOCK_REALTIME, 0,
+ timeout);
+ if (error != 0)
+ return (error);
+ }
/*
* Care must be exercised when dealing with umtx structure. It
@@ -1109,8 +1171,12 @@
uq = td->td_umtxq;
- if (timeout != NULL)
- umtx_abs_timeout_init(&timo, CLOCK_REALTIME, 0, timeout);
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init(&timo, CLOCK_REALTIME, 0,
+ timeout);
+ if (error != 0)
+ return (error);
+ }
/*
* Care must be exercised when dealing with umtx structure. It
@@ -1288,14 +1354,17 @@
uint32_t tmp32;
int error = 0;
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
+
uq = td->td_umtxq;
if ((error = umtx_key_get(addr, TYPE_SIMPLE_WAIT,
is_private ? THREAD_SHARE : AUTO_SHARE, &uq->uq_key)) != 0)
return (error);
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
-
umtxq_lock(&uq->uq_key);
umtxq_insert(uq);
umtxq_unlock(&uq->uq_key);
@@ -1363,8 +1432,11 @@
id = td->td_tid;
uq = td->td_umtxq;
error = 0;
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
/*
* Care must be exercised when dealing with umtx structure. It
@@ -2250,6 +2322,12 @@
uint32_t id, old_owner, owner, old;
int error, rv;
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
+
id = td->td_tid;
uq = td->td_umtxq;
@@ -2258,9 +2336,6 @@
&uq->uq_key)) != 0)
return (error);
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
-
umtxq_lock(&uq->uq_key);
pi = umtx_pi_lookup(&uq->uq_key);
if (pi == NULL) {
@@ -2547,6 +2622,12 @@
int error, pri, old_inherited_pri, new_pri, rv;
bool su;
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
+
id = td->td_tid;
uq = td->td_umtxq;
if ((error = umtx_key_get(m, (flags & UMUTEX_ROBUST) != 0 ?
@@ -2554,9 +2635,6 @@
&uq->uq_key)) != 0)
return (error);
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
-
su = (priv_check(td, PRIV_SCHED_RTPRIO) == 0);
for (;;) {
old_inherited_pri = uq->uq_inherited_pri;
@@ -3090,6 +3168,12 @@
int32_t blocked_readers;
int error, error1, rv;
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
+
uq = td->td_umtxq;
error = fueword32(&rwlock->rw_flags, &flags);
if (error == -1)
@@ -3098,9 +3182,6 @@
if (error != 0)
return (error);
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
-
wrflags = URWLOCK_WRITE_OWNER;
if (!(fflag & URWLOCK_PREFER_READER) && !(flags & URWLOCK_PREFER_READER))
wrflags |= URWLOCK_WRITE_WAITERS;
@@ -3278,6 +3359,12 @@
int32_t blocked_readers;
int error, error1, rv;
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
+
uq = td->td_umtxq;
error = fueword32(&rwlock->rw_flags, &flags);
if (error == -1)
@@ -3286,9 +3373,6 @@
if (error != 0)
return (error);
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
-
blocked_readers = 0;
for (;;) {
rv = fueword32(&rwlock->rw_state, &state);
@@ -3572,6 +3656,12 @@
uint32_t flags, count, count1;
int error, rv, rv1;
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
+
uq = td->td_umtxq;
error = fueword32(&sem->_flags, &flags);
if (error == -1)
@@ -3580,9 +3670,6 @@
if (error != 0)
return (error);
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
-
again:
umtxq_lock(&uq->uq_key);
umtxq_busy(&uq->uq_key);
@@ -3683,9 +3770,11 @@
uq = td->td_umtxq;
flags = fuword32(&sem->_flags);
- if (timeout != NULL)
- umtx_abs_timeout_init2(&timo, timeout);
-
+ if (timeout != NULL) {
+ error = umtx_abs_timeout_init2(&timo, timeout);
+ if (error != 0)
+ return (error);
+ }
again:
error = umtx_key_get(sem, TYPE_SEM, GET_SHARE(flags), &uq->uq_key);
if (error != 0)
@@ -4143,6 +4232,7 @@
(wflags & CVWAIT_UMTX_TIME) != 0))
return (EINVAL);
+ error = 0;
if ((wflags & CVWAIT_UMTX_TIME) == 0) {
/* Allow a null timespec (wait forever). */
if (uap->uaddr2 == NULL) {
@@ -4161,8 +4251,10 @@
clockid = CLOCK_REALTIME;
}
if (ts != NULL) {
- umtx_abs_timeout_init(&timo, clockid,
+ error = umtx_abs_timeout_init(&timo, clockid,
(wflags & CVWAIT_ABSTIME) != 0, ts);
+ if (error != 0)
+ return (error);
timop = &timo;
} else {
timop = NULL;
@@ -4175,13 +4267,10 @@
if (error != 0)
return (error);
timop = &timo;
- umtx_abs_timeout_init2(timop, &umtime);
+ error = umtx_abs_timeout_init2(timop, &umtime);
+ if (error != 0)
+ return (error);
}
- /* only HW clock id will work. */
- if (timop != NULL && (timop->clockid < CLOCK_REALTIME ||
- timop->clockid >= CLOCK_THREAD_CPUTIME_ID) &&
- timop->clockid != CLOCK_TAI)
- return (EINVAL);
return (do_cv_wait(td, cv, uap->uaddr1, timop, wflags));
}
diff --git a/sys/sys/umtxvar.h b/sys/sys/umtxvar.h
--- a/sys/sys/umtxvar.h
+++ b/sys/sys/umtxvar.h
@@ -198,7 +198,7 @@
k1->info.both.b == k2->info.both.b);
}
-void umtx_abs_timeout_init(struct umtx_abs_timeout *, int, int,
+int umtx_abs_timeout_init(struct umtx_abs_timeout *, int, int,
const struct timespec *);
int umtx_copyin_timeout(const void *, struct timespec *);
void umtx_exec(struct proc *p);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 15, 3:25 AM (23 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41733964
Default Alt Text
D61253.1794713101.diff (68 KB)
Attached To
Mode
D61253: Implement pthread_mutex_clocklock(3) and pthread_rwlock_clock{wr,rd}lock(3) from POSIX 2024
Attached
Detach File
Event Timeline