[no subject]
From: sdolan
Date: Mon Mar 30 2026 - 12:37:06 EST
When nanosleep or clock_nanosleep are interrupted by a signal, they
return EINTR and the remaining time, so that the caller can resume the
sleep afterwards. However, with a high rate of signals, nanosleep
currently ends up sleeping for much longer than requested. Here's an
example program that tries to sleep for 1s with 10k signals/sec, and
ends up sleeping for >2.1s:
#include <signal.h>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
static void handler(int sig) {(void)sig;}
int main() {
signal(SIGALRM, handler);
struct timeval intv = { .tv_sec = 0, .tv_usec = 100 };
struct itimerval it = { .it_interval = intv, .it_value = intv };
setitimer(ITIMER_REAL, &it, 0);
struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 };
int rc;
do {
rc = nanosleep(&ts, &ts);
} while (rc < 0 && errno == EINTR);
}
The manpage for nanosleep points out that using relative timeouts here
can cause drift, but it shouldn't take 1.1 extra seconds to handle 10k
signals.
The problem is in do_nanosleep: the 'expires' field of the hrtimer used
for the sleep is (now + timeout + timerslack), which is by default 50 us
later than the requested end time. This field is used to compute the
remaining time, which means that 50 us is added to the wakeup time every
time nanosleep returns EINTR. Since the example has signals arriving
every 100us, this causes a much later wakeup than intended. If the
example is modified to have an interval between signals shorter than
50us (or whatever timerslack is set to), then the nanosleep loop will
never complete.
Instead, the remaining time should be computed from _softexpires, which
is the original deadline before timerslack is added. This patch does
that, which makes the example above run in about 1.040 seconds. (The
extra 40ms is the drift the manpage was warning about, and is more in
line with how long it takes to handle 10k signals).
Signed-off-by: Stephen Dolan <sdolan@xxxxxxxxxxxxxx>
---
include/linux/hrtimer.h | 5 +++++
kernel/time/hrtimer.c | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h
index 74adbd4e7003..c6f0b9ad3dc3 100644
--- a/include/linux/hrtimer.h
+++ b/include/linux/hrtimer.h
@@ -146,6 +146,11 @@ static inline ktime_t hrtimer_expires_remaining(const struct hrtimer *timer)
return ktime_sub(timer->node.expires, hrtimer_cb_get_time(timer));
}
+static inline ktime_t hrtimer_softexpires_remaining(const struct hrtimer *timer)
+{
+ return ktime_sub(timer->_softexpires, hrtimer_cb_get_time(timer));
+}
+
static inline int hrtimer_is_hres_active(struct hrtimer *timer)
{
return IS_ENABLED(CONFIG_HIGH_RES_TIMERS) ?
diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c
index 860af7a58428..12310967ca84 100644
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -2135,7 +2135,7 @@ static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mod
restart = ¤t->restart_block;
if (restart->nanosleep.type != TT_NONE) {
- ktime_t rem = hrtimer_expires_remaining(&t->timer);
+ ktime_t rem = hrtimer_softexpires_remaining(&t->timer);
struct timespec64 rmt;
if (rem <= 0)
--
2.47.3