Module Name: src Committed By: riastradh Date: Sun May 3 01:24:38 UTC 2020
Modified Files: src/sys/kern: kern_condvar.c src/sys/sys: condvar.h Log Message: New cv_timedwaitclock, cv_timedwaitclock_sig. Usage: given a struct timespec timeout copied from userland, along with a clockid and TIMER_* flags, error = cv_timedwaitclock(cv, lock, timeout, clockid, flags, DEFAULT_TIMEOUT_EPSILON); if (error) /* fail */ If flags is relative (i.e., (flags & TIMER_ABSTIME) == 0), then this deducts the time spent waiting from timeout, so you can run it in a loop: struct timespec timeout; error = copyin(SCARG(uap, timeout), &timeout, sizeof timeout); if (error) return error; mutex_enter(lock); while (!ready()) { error = cv_timedwaitclock_sig(cv, lock, &timeout, SCARG(uap, clockid), SCARG(uap, flags), DEFAULT_TIMEOUT_EPSILON); if (error) break; } mutex_exit(lock); CAVEAT: If the system call is interrupted by a signal with SA_RESTART so cv_timedwaitclock_sig fails with ERESTART, then the system call will be restarted with the _original_ relative timeout, not counting the time that was already spent waiting. This is a problem but it's not a problem I want to deal with at the moment. To generate a diff of this commit: cvs rdiff -u -r1.48 -r1.49 src/sys/kern/kern_condvar.c cvs rdiff -u -r1.15 -r1.16 src/sys/sys/condvar.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/kern/kern_condvar.c diff -u src/sys/kern/kern_condvar.c:1.48 src/sys/kern/kern_condvar.c:1.49 --- src/sys/kern/kern_condvar.c:1.48 Sun May 3 01:19:47 2020 +++ src/sys/kern/kern_condvar.c Sun May 3 01:24:37 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: kern_condvar.c,v 1.48 2020/05/03 01:19:47 riastradh Exp $ */ +/* $NetBSD: kern_condvar.c,v 1.49 2020/05/03 01:24:37 riastradh Exp $ */ /*- * Copyright (c) 2006, 2007, 2008, 2019, 2020 The NetBSD Foundation, Inc. @@ -34,7 +34,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: kern_condvar.c,v 1.48 2020/05/03 01:19:47 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: kern_condvar.c,v 1.49 2020/05/03 01:24:37 riastradh Exp $"); #include <sys/param.h> #include <sys/systm.h> @@ -244,6 +244,189 @@ cv_timedwait_sig(kcondvar_t *cv, kmutex_ return error; } +struct timedwaitclock { + struct timespec *timeout; + clockid_t clockid; + int flags; + const struct bintime *epsilon; + struct timespec starttime; +}; + +static int +cv_timedwaitclock_begin(struct timedwaitclock *T, int *timo) +{ + struct timespec delta; + const struct timespec *deltap; + int error; + + /* Sanity-check timeout -- may have come from userland. */ + if (T->timeout->tv_nsec < 0 || T->timeout->tv_nsec >= 1000000000L) + return EINVAL; + + /* + * Compute the time delta. + */ + if ((T->flags & TIMER_ABSTIME) == TIMER_ABSTIME) { + /* Check our watch. */ + error = clock_gettime1(T->clockid, &T->starttime); + if (error) + return error; + + /* If the deadline has passed, we're done. */ + if (timespeccmp(T->timeout, &T->starttime, <=)) + return ETIMEDOUT; + + /* Count how much time is left. */ + timespecsub(T->timeout, &T->starttime, &delta); + deltap = δ + } else { + /* The user specified how much time is left. */ + deltap = T->timeout; + + /* If there's none left, we've timed out. */ + if (deltap->tv_sec == 0 && deltap->tv_nsec == 0) + return ETIMEDOUT; + } + + /* + * Convert to ticks, but clamp to be >=1. + * + * XXX In the tickless future, use a high-resolution timer if + * timo would round to zero. + */ + *timo = tstohz(deltap); + KASSERTMSG(*timo >= 0, "negative ticks: %d", *timo); + if (*timo == 0) + *timo = 1; + + /* Success! */ + return 0; +} + +static void +cv_timedwaitclock_end(struct timedwaitclock *T) +{ + struct timespec endtime, delta; + + /* If the timeout is absolute, nothing to do. */ + if ((T->flags & TIMER_ABSTIME) == TIMER_ABSTIME) + return; + + /* + * Check our watch. If anything goes wrong with it, make sure + * that the next time we immediately time out rather than fail + * to deduct the time elapsed. + */ + if (clock_gettime1(T->clockid, &endtime)) { + T->timeout->tv_sec = 0; + T->timeout->tv_nsec = 0; + return; + } + + /* Find how much time elapsed while we waited. */ + timespecsub(&endtime, &T->starttime, &delta); + + /* + * Paranoia: If the clock went backwards, treat it as if no + * time elapsed at all rather than adding anything. + */ + if (delta.tv_sec < 0 || + (delta.tv_sec == 0 && delta.tv_nsec < 0)) { + delta.tv_sec = 0; + delta.tv_nsec = 0; + } + + /* + * Set it to the time left, or zero, whichever is larger. We + * do not fail with EWOULDBLOCK here because this may have been + * an explicit wakeup, so the caller needs to check before they + * give up or else cv_signal would be lost. + */ + if (timespeccmp(T->timeout, &delta, <=)) { + T->timeout->tv_sec = 0; + T->timeout->tv_nsec = 0; + } else { + timespecsub(T->timeout, &delta, T->timeout); + } +} + +/* + * cv_timedwaitclock: + * + * Wait on a condition variable until awoken normally, or the + * specified timeout expires according to the provided clock. + * Returns zero if awoken normally or EWOULDBLOCK if the timeout + * expired. For relative timeouts ((flags & TIMER_ABSTIME) == 0), + * updates timeout with the time left. + * + * timeout == NULL specifies an infinite timeout. epsilon is a + * requested maximum error in timeout (excluding spurious + * wakeups). + */ +int +cv_timedwaitclock(kcondvar_t *cv, kmutex_t *mtx, struct timespec *timeout, + clockid_t clockid, int flags, const struct bintime *epsilon) +{ + struct timedwaitclock T = { + .timeout = timeout, + .clockid = clockid, + .flags = flags, + .epsilon = epsilon, + }; + int timo; + int error; + + if (timeout == NULL) { + cv_wait(cv, mtx); + return 0; + } + + error = cv_timedwaitclock_begin(&T, &timo); + if (error) + return error; + error = cv_timedwait(cv, mtx, timo); + cv_timedwaitclock_end(&T); + return error; +} + +/* + * cv_timedwaitclock_sig: + * + * Wait on a condition variable until awoken normally, interrupted + * by a signal, or the specified timeout expires according to the + * provided clock. Returns zero if awoken normally, + * EINTR/ERESTART if interrupted by a signal, or EWOULDBLOCK if + * the timeout expired. For relative timeouts ((flags & + * TIMER_ABSTIME) == 0), updates timeout with the time left. + * + * timeout == NULL specifies an infinite timeout. epsilon is a + * requested maximum error in timeout (excluding spurious + * wakeups). + */ +int +cv_timedwaitclock_sig(kcondvar_t *cv, kmutex_t *mtx, struct timespec *timeout, + clockid_t clockid, int flags, const struct bintime *epsilon) +{ + struct timedwaitclock T = { + .timeout = timeout, + .clockid = clockid, + .flags = flags, + .epsilon = epsilon, + }; + int timo; + int error; + + if (timeout == NULL) + return cv_wait_sig(cv, mtx); + + error = cv_timedwaitclock_begin(&T, &timo); + if (error) + return error; + error = cv_timedwait_sig(cv, mtx, timo); + cv_timedwaitclock_end(&T); + return error; +} + /* * Given a number of seconds, sec, and 2^64ths of a second, frac, we * want a number of ticks for a timeout: Index: src/sys/sys/condvar.h diff -u src/sys/sys/condvar.h:1.15 src/sys/sys/condvar.h:1.16 --- src/sys/sys/condvar.h:1.15 Thu Mar 26 19:46:42 2020 +++ src/sys/sys/condvar.h Sun May 3 01:24:37 2020 @@ -1,4 +1,4 @@ -/* $NetBSD: condvar.h,v 1.15 2020/03/26 19:46:42 ad Exp $ */ +/* $NetBSD: condvar.h,v 1.16 2020/05/03 01:24:37 riastradh Exp $ */ /*- * Copyright (c) 2006, 2007, 2008, 2020 The NetBSD Foundation, Inc. @@ -40,6 +40,7 @@ typedef struct kcondvar { struct bintime; struct kmutex; +struct timespec; void cv_init(kcondvar_t *, const char *); void cv_destroy(kcondvar_t *); @@ -48,6 +49,10 @@ void cv_wait(kcondvar_t *, struct kmutex int cv_wait_sig(kcondvar_t *, struct kmutex *); int cv_timedwait(kcondvar_t *, struct kmutex *, int); int cv_timedwait_sig(kcondvar_t *, struct kmutex *, int); +int cv_timedwaitclock(kcondvar_t *, struct kmutex *, struct timespec *, + clockid_t, int, const struct bintime *); +int cv_timedwaitclock_sig(kcondvar_t *, struct kmutex *, struct timespec *, + clockid_t, int, const struct bintime *); int cv_timedwaitbt(kcondvar_t *, struct kmutex *, struct bintime *, const struct bintime *); int cv_timedwaitbt_sig(kcondvar_t *, struct kmutex *, struct bintime *,