Page MenuHomeFreeBSD

D61253.1794713101.diff
No OneTemporary

Authored By
Unknown
Size
68 KB
Referenced Files
None
Subscribers
None

D61253.1794713101.diff

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

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)

Event Timeline