Support modifying an existing timer without having to delete it first.

Semantics match FreeBSD.
This commit is contained in:
thorpej 2021-10-22 04:49:24 +00:00
parent 198576fe49
commit 317dcfdb56
2 changed files with 172 additions and 10 deletions

View File

@ -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 *, filedesc_t *fdp, bool);
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_filtops = {
.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);
@ -1331,6 +1345,61 @@ filt_timerdetach(struct knote *kn)
atomic_dec_uint(&kq_ncallouts);
}
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)
{
@ -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).

View File

@ -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();
}