Module Name:    src
Committed By:   thorpej
Date:           Fri Oct 22 04:49:24 UTC 2021

Modified Files:
        src/sys/kern: kern_event.c
        src/tests/kernel/kqueue: t_timer.c

Log Message:
Support modifying an existing timer without having to delete it first.
Semantics match FreeBSD.


To generate a diff of this commit:
cvs rdiff -u -r1.135 -r1.136 src/sys/kern/kern_event.c
cvs rdiff -u -r1.1 -r1.2 src/tests/kernel/kqueue/t_timer.c

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_event.c
diff -u src/sys/kern/kern_event.c:1.135 src/sys/kern/kern_event.c:1.136
--- src/sys/kern/kern_event.c:1.135	Thu Oct 21 02:34:03 2021
+++ src/sys/kern/kern_event.c	Fri Oct 22 04:49:24 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: kern_event.c,v 1.135 2021/10/21 02:34:03 thorpej Exp $	*/
+/*	$NetBSD: kern_event.c,v 1.136 2021/10/22 04:49:24 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2008, 2009, 2021 The NetBSD Foundation, Inc.
@@ -63,7 +63,7 @@
 #endif /* _KERNEL_OPT */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_event.c,v 1.135 2021/10/21 02:34:03 thorpej Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_event.c,v 1.136 2021/10/22 04:49:24 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -102,6 +102,7 @@ static void	knote_detach(struct knote *,
 static void	knote_enqueue(struct knote *);
 static void	knote_activate(struct knote *);
 static void	knote_activate_locked(struct knote *);
+static void	knote_deactivate_locked(struct knote *);
 
 static void	filt_kqdetach(struct knote *);
 static int	filt_kqueue(struct knote *, long hint);
@@ -113,6 +114,7 @@ static void	filt_timerexpire(void *x);
 static int	filt_timerattach(struct knote *);
 static void	filt_timerdetach(struct knote *);
 static int	filt_timer(struct knote *, long hint);
+static int	filt_timertouch(struct knote *, struct kevent *, long type);
 static int	filt_userattach(struct knote *);
 static void	filt_userdetach(struct knote *);
 static int	filt_user(struct knote *, long hint);
@@ -163,6 +165,7 @@ static const struct filterops timer_filt
 	.f_attach = filt_timerattach,
 	.f_detach = filt_timerdetach,
 	.f_event = filt_timer,
+	.f_touch = filt_timertouch,
 };
 
 static const struct filterops user_filtops = {
@@ -1261,6 +1264,22 @@ filt_timerexpire(void *knx)
 	mutex_spin_exit(&kq->kq_lock);
 }
 
+static inline void
+filt_timerstart(struct knote *kn, uintptr_t tticks)
+{
+	callout_t *calloutp = kn->kn_hook;
+
+	KASSERT(mutex_owned(&kn->kn_kq->kq_lock));
+	KASSERT(!callout_pending(calloutp));
+
+	if (__predict_false(tticks == FILT_TIMER_NOSCHED)) {
+		kn->kn_data = 1;
+	} else {
+		KASSERT(tticks <= INT_MAX);
+		callout_reset(calloutp, (int)tticks, filt_timerexpire, kn);
+	}
+}
+
 static int
 filt_timerattach(struct knote *kn)
 {
@@ -1295,12 +1314,7 @@ filt_timerattach(struct knote *kn)
 	KASSERT(kn->kn_sfflags == kev.fflags);
 	kn->kn_hook = calloutp;
 
-	if (__predict_false(tticks == FILT_TIMER_NOSCHED)) {
-		kn->kn_data = 1;
-	} else {
-		KASSERT(tticks <= INT_MAX);
-		callout_reset(calloutp, (int)tticks, filt_timerexpire, kn);
-	}
+	filt_timerstart(kn, tticks);
 
 	mutex_spin_exit(&kq->kq_lock);
 
@@ -1332,6 +1346,61 @@ filt_timerdetach(struct knote *kn)
 }
 
 static int
+filt_timertouch(struct knote *kn, struct kevent *kev, long type)
+{
+	struct kqueue *kq = kn->kn_kq;
+	callout_t *calloutp;
+	uintptr_t tticks;
+	int error;
+
+	KASSERT(mutex_owned(&kq->kq_lock));
+
+	switch (type) {
+	case EVENT_REGISTER:
+		/* Only relevant for EV_ADD. */
+		if ((kev->flags & EV_ADD) == 0) {
+			return 0;
+		}
+
+		/*
+		 * Stop the timer, under the assumption that if
+		 * an application is re-configuring the timer,
+		 * they no longer care about the old one.  We
+		 * can safely drop the kq_lock while we wait
+		 * because fdp->fd_lock will be held throughout,
+		 * ensuring that no one can sneak in with an
+		 * EV_DELETE or close the kq.
+		 */
+		KASSERT(mutex_owned(&kq->kq_fdp->fd_lock));
+
+		calloutp = kn->kn_hook;
+		callout_halt(calloutp, &kq->kq_lock);
+		KASSERT(mutex_owned(&kq->kq_lock));
+		knote_deactivate_locked(kn);
+		kn->kn_data = 0;
+
+		error = filt_timercompute(kev, &tticks);
+		if (error) {
+			return error;
+		}
+		kn->kn_sdata = kev->data;
+		kn->kn_flags = kev->flags;
+		kn->kn_sfflags = kev->fflags;
+		filt_timerstart(kn, tticks);
+		break;
+
+	case EVENT_PROCESS:
+		*kev = kn->kn_kevent;
+		break;
+
+	default:
+		panic("%s: invalid type (%ld)", __func__, type);
+	}
+
+	return 0;
+}
+
+static int
 filt_timer(struct knote *kn, long hint)
 {
 	struct kqueue *kq = kn->kn_kq;
@@ -2686,6 +2755,22 @@ knote_activate(struct knote *kn)
 	mutex_spin_exit(&kq->kq_lock);
 }
 
+static void
+knote_deactivate_locked(struct knote *kn)
+{
+	struct kqueue *kq = kn->kn_kq;
+
+	if (kn->kn_status & KN_QUEUED) {
+		kq_check(kq);
+		kn->kn_status &= ~KN_QUEUED;
+		TAILQ_REMOVE(&kq->kq_head, kn, kn_tqe);
+		KASSERT(KQ_COUNT(kq) > 0);
+		kq->kq_count--;
+		kq_check(kq);
+	}
+	kn->kn_status &= ~KN_ACTIVE;
+}
+
 /*
  * Set EV_EOF on the specified knote.  Also allows additional
  * EV_* flags to be set (e.g. EV_ONESHOT).

Index: src/tests/kernel/kqueue/t_timer.c
diff -u src/tests/kernel/kqueue/t_timer.c:1.1 src/tests/kernel/kqueue/t_timer.c:1.2
--- src/tests/kernel/kqueue/t_timer.c:1.1	Wed Oct 13 04:57:19 2021
+++ src/tests/kernel/kqueue/t_timer.c	Fri Oct 22 04:49:24 2021
@@ -1,4 +1,4 @@
-/* $NetBSD: t_timer.c,v 1.1 2021/10/13 04:57:19 thorpej Exp $ */
+/* $NetBSD: t_timer.c,v 1.2 2021/10/22 04:49:24 thorpej Exp $ */
 
 /*-
  * Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: t_timer.c,v 1.1 2021/10/13 04:57:19 thorpej Exp $");
+__RCSID("$NetBSD: t_timer.c,v 1.2 2021/10/22 04:49:24 thorpej Exp $");
 
 #include <sys/types.h>
 #include <sys/event.h>
@@ -170,6 +170,82 @@ ATF_TC_BODY(count_expirations, tc)
 		    event[0].data == TIME1_COUNT + 1);
 }
 
+ATF_TC(modify);
+ATF_TC_HEAD(modify, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "tests modifying a timer");
+}
+
+ATF_TC_BODY(modify, tc)
+{
+	struct kevent event[1];
+	struct timespec ts = { 0, 0 };
+	struct timespec sleepts;
+	int kq;
+
+	ATF_REQUIRE((kq = kqueue()) >= 0);
+
+	/*
+	 * Start a 500ms timer, sleep for 5 seconds, and check
+	 * the total count.
+	 */
+	EV_SET(&event[0], 1, EVFILT_TIMER, EV_ADD, 0, 500, NULL);
+	ATF_REQUIRE(kevent(kq, event, 1, NULL, 0, NULL) == 0);
+
+	sleepts.tv_sec = 5;
+	sleepts.tv_nsec = 0;
+	ATF_REQUIRE(nanosleep(&sleepts, NULL) == 0);
+
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].ident == 1);
+	ATF_REQUIRE(event[0].data >= 9 && event[0].data <= 11);
+
+	/*
+	 * Modify to a 4 second timer, sleep for 5 seconds, and check
+	 * the total count.
+	 */
+	EV_SET(&event[0], 1, EVFILT_TIMER, EV_ADD, 0, 4000, NULL);
+	ATF_REQUIRE(kevent(kq, event, 1, NULL, 0, NULL) == 0);
+
+	sleepts.tv_sec = 5;
+	sleepts.tv_nsec = 0;
+	ATF_REQUIRE(nanosleep(&sleepts, NULL) == 0);
+
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].ident == 1);
+	ATF_REQUIRE(event[0].data == 1);
+
+	/*
+	 * Start a 500ms timer, sleep for 2 seconds.
+	 */
+	EV_SET(&event[0], 1, EVFILT_TIMER, EV_ADD, 0, 500, NULL);
+	ATF_REQUIRE(kevent(kq, event, 1, NULL, 0, NULL) == 0);
+
+	sleepts.tv_sec = 2;
+	sleepts.tv_nsec = 0;
+	ATF_REQUIRE(nanosleep(&sleepts, NULL) == 0);
+
+	/*
+	 * Set the SAME timer, sleep for 2 seconds.
+	 */
+	EV_SET(&event[0], 1, EVFILT_TIMER, EV_ADD, 0, 500, NULL);
+	ATF_REQUIRE(kevent(kq, event, 1, NULL, 0, NULL) == 0);
+
+	sleepts.tv_sec = 2;
+	sleepts.tv_nsec = 0;
+	ATF_REQUIRE(nanosleep(&sleepts, NULL) == 0);
+
+	/*
+	 * The kernel should have reset the count when modifying the
+	 * timer, so we should only expect to see the expiration count
+	 * for the second sleep.
+	 */
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].ident == 1);
+	ATF_REQUIRE(event[0].data >= 3 && event[0].data <= 5);
+}
+
 ATF_TC(abstime);
 ATF_TC_HEAD(abstime, tc)
 {
@@ -269,6 +345,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, count_expirations);
 	ATF_TP_ADD_TC(tp, abstime);
 	ATF_TP_ADD_TC(tp, timer_units);
+	ATF_TP_ADD_TC(tp, modify);
 
 	return atf_no_error();
 }

Reply via email to