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 = &delta;
+	} 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 *,

Reply via email to