aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Freeland <jfree@FreeBSD.org>2026-03-20 06:33:03 +0000
committerJake Freeland <jfree@FreeBSD.org>2026-04-03 15:25:36 +0000
commit9b785380f307e772eae0df017c982acd81d5879e (patch)
tree78c161d0e27155f9a6827b924ed1daef9f9c6e28
parentc484a2dc47cccd894e9d7b1fd0a64b2051cb4861 (diff)
timerfd: Fix interval callout scheduling
When a timerfd interval callout misses its scheduled activation time, a differential is calculated based on the actual activation time and the scheduled activation time. This differential is divided by the timerfd's interval time and the quotient is added to the timerfd's counter. Before this change, the next callout was scheduled to activate at: scheduled activation time + timerfd interval. This change fixes the scheduling of the next callout to activate at: actual activation time + timerfd interval - remainder. Reviewed by: markj Differential Revision: https://reviews.freebsd.org/D55790 MFC after: 2 weeks (cherry picked from commit 85c0f1a87da1fd1eb3e646e86f70e630c48da91a)
-rw-r--r--sys/kern/sys_timerfd.c24
-rw-r--r--tests/sys/kern/timerfd.c29
2 files changed, 42 insertions, 11 deletions
diff --git a/sys/kern/sys_timerfd.c b/sys/kern/sys_timerfd.c
index 565ab3ad6ee6..236dfe8bb96a 100644
--- a/sys/kern/sys_timerfd.c
+++ b/sys/kern/sys_timerfd.c
@@ -393,23 +393,25 @@ static void
timerfd_expire(void *arg)
{
struct timerfd *tfd = (struct timerfd *)arg;
- struct timespec uptime;
+ sbintime_t exp, interval, now, next, diff;
++tfd->tfd_count;
tfd->tfd_expired = true;
if (timespecisset(&tfd->tfd_time.it_interval)) {
+ exp = tstosbt(tfd->tfd_time.it_value);
+ interval = tstosbt(tfd->tfd_time.it_interval);
+ now = sbinuptime();
+ next = now + interval;
+
/* Count missed events. */
- nanouptime(&uptime);
- if (timespeccmp(&uptime, &tfd->tfd_time.it_value, >)) {
- timespecsub(&uptime, &tfd->tfd_time.it_value, &uptime);
- tfd->tfd_count += tstosbt(uptime) /
- tstosbt(tfd->tfd_time.it_interval);
+ if (now > exp) {
+ diff = now - exp;
+ tfd->tfd_count += diff / interval;
+ next -= diff % interval;
}
- timespecadd(&tfd->tfd_time.it_value,
- &tfd->tfd_time.it_interval, &tfd->tfd_time.it_value);
- callout_schedule_sbt(&tfd->tfd_callout,
- tstosbt(tfd->tfd_time.it_value),
- 0, C_ABSOLUTE);
+
+ callout_schedule_sbt(&tfd->tfd_callout, next, 0, C_ABSOLUTE);
+ tfd->tfd_time.it_value = sbttots(next);
} else {
/* Single shot timer. */
callout_deactivate(&tfd->tfd_callout);
diff --git a/tests/sys/kern/timerfd.c b/tests/sys/kern/timerfd.c
index 37cb4924faf1..b24d093b346e 100644
--- a/tests/sys/kern/timerfd.c
+++ b/tests/sys/kern/timerfd.c
@@ -949,6 +949,34 @@ ATF_TC_BODY(timerfd__reset_to_very_long, tc)
ATF_REQUIRE(errno == EAGAIN);
}
+ATF_TC_WITHOUT_HEAD(timerfd__missed_events);
+ATF_TC_BODY(timerfd__missed_events, tc)
+{
+ struct itimerspec its = { };
+ uint64_t timeouts;
+ int timerfd;
+
+ timerfd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
+ ATF_REQUIRE(timerfd >= 0);
+
+ ATF_REQUIRE(clock_gettime(CLOCK_REALTIME, &its.it_value) == 0);
+ its.it_value.tv_sec -= 1000;
+ its.it_interval.tv_sec = 1;
+
+ ATF_REQUIRE(timerfd_settime(timerfd, TFD_TIMER_ABSTIME, &its,
+ NULL) == 0);
+
+ ATF_REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) ==
+ sizeof(timeouts));
+ ATF_REQUIRE_MSG(timeouts == 1001, "%ld", (long)timeouts);
+
+ ATF_REQUIRE(read(timerfd, &timeouts, sizeof(timeouts)) ==
+ sizeof(timeouts));
+ ATF_REQUIRE_MSG(timeouts == 1, "%ld", (long)timeouts);
+
+ ATF_REQUIRE(close(timerfd) == 0);
+}
+
/*
* Tests requiring root (clock_settime on CLOCK_REALTIME).
* Tests gracefully skip if not running as root.
@@ -1306,6 +1334,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, timerfd__short_evfilt_timer_timeout);
ATF_TP_ADD_TC(tp, timerfd__unmodified_errno);
ATF_TP_ADD_TC(tp, timerfd__reset_to_very_long);
+ ATF_TP_ADD_TC(tp, timerfd__missed_events);
ATF_TP_ADD_TC(tp, timerfd_root__zero_read_on_abs_realtime);
ATF_TP_ADD_TC(tp, timerfd_root__read_on_abs_realtime_no_interval);