[hackers] [PATCH slstatus] align ticks to wall-clock seconds

From: Jialu Xu <xujialu_AT_vimux.org>
Date: Mon, 4 May 2026 19:44:51 +0800

Replace the relative CLOCK_MONOTONIC nanosleep loop with absolute
CLOCK_REALTIME deadlines via clock_nanosleep(TIMER_ABSTIME). Anchor
the first tick to the next whole-second boundary; advance by
interval each cycle, snapping forward after large gaps (suspend,
NTP step). Only advance when the deadline has actually elapsed,
so SIGUSR1 force-refresh inserts a bonus render without skipping
the next scheduled tick.

Switch datetime to clock_gettime(CLOCK_REALTIME) -- glibc's time()
reads CLOCK_REALTIME_COARSE, which lags by up to one jiffy and
truncates the displayed second backwards at the boundary.

Signed-off-by: Jialu Xu <xujialu_AT_vimux.org>
---
 components/datetime.c | 15 ++++++---
 slstatus.c            | 71 ++++++++++++++++++++++++++++---------------
 2 files changed, 57 insertions(+), 29 deletions(-)
diff --git a/components/datetime.c b/components/datetime.c
index 5b10daf..0b8107f 100644
--- a/components/datetime.c
+++ b/components/datetime.c
_AT_@ -1,5 +1,4 @@
 /* See LICENSE file for copyright and license details. */
-#include <stdio.h>
 #include <time.h>
 
 #include "../slstatus.h"
_AT_@ -8,10 +7,18 @@
 const char *
 datetime(const char *fmt)
 {
-	time_t t;
+	struct timespec ts;
 
-	t = time(NULL);
-	if (!strftime(buf, sizeof(buf), fmt, localtime(&t))) {
+	/* Use CLOCK_REALTIME (high-resolution) rather than time(),
+	 * which on glibc reads CLOCK_REALTIME_COARSE and lags the
+	 * precise clock by up to one jiffy. At a whole-second
+	 * boundary the lag truncates the displayed second backwards
+	 * by one, making the clock appear to tick a second late. */
+	if (clock_gettime(CLOCK_REALTIME, &ts) < 0) {
+		warn("clock_gettime:");
+		return NULL;
+	}
+	if (!strftime(buf, sizeof(buf), fmt, localtime(&ts.tv_sec))) {
 		warn("strftime: Result string exceeds buffer size");
 		return NULL;
 	}
diff --git a/slstatus.c b/slstatus.c
index 16d88fe..4ae50f8 100644
--- a/slstatus.c
+++ b/slstatus.c
_AT_@ -30,14 +30,6 @@ terminate(const int signo)
 		done = 1;
 }
 
-static void
-difftimespec(struct timespec *res, struct timespec *a, struct timespec *b)
-{
-	res->tv_sec = a->tv_sec - b->tv_sec - (a->tv_nsec < b->tv_nsec);
-	res->tv_nsec = a->tv_nsec - b->tv_nsec +
-	               (a->tv_nsec < b->tv_nsec) * 1E9;
-}
-
 static void
 usage(void)
 {
_AT_@ -48,7 +40,7 @@ int
 main(int argc, char *argv[])
 {
 	struct sigaction act;
-	struct timespec start, current, diff, intspec, wait;
+	struct timespec next, now;
 	size_t i, len;
 	int sflag, ret;
 	char status[MAXLEN];
_AT_@ -82,10 +74,17 @@ main(int argc, char *argv[])
 	if (!sflag && !(dpy = XOpenDisplay(NULL)))
 		die("XOpenDisplay: Failed to open display");
 
-	do {
-		if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
-			die("clock_gettime:");
-
+	/* Anchor the first scheduled tick to the next whole-second
+	 * boundary on the wall clock. Subsequent ticks use absolute
+	 * CLOCK_REALTIME deadlines advanced by `interval`, so the
+	 * displayed phase stays locked to wall-clock seconds across
+	 * NTP adjustments and suspend/resume. */
+	if (clock_gettime(CLOCK_REALTIME, &next) < 0)
+		die("clock_gettime:");
+	next.tv_nsec = 0;
+	next.tv_sec += 1;
+
+	for (;;) {
 		status[0] = '\0';
 		for (i = len = 0; i < LEN(args); i++) {
 			if (!(res = args[i].func(args[i].args)))
_AT_@ -109,21 +108,43 @@ main(int argc, char *argv[])
 			XFlush(dpy);
 		}
 
-		if (!done) {
-			if (clock_gettime(CLOCK_MONOTONIC, &current) < 0)
-				die("clock_gettime:");
-			difftimespec(&diff, &current, &start);
+		if (done)
+			break;
 
-			intspec.tv_sec = interval / 1000;
-			intspec.tv_nsec = (interval % 1000) * 1E6;
-			difftimespec(&wait, &intspec, &diff);
+		ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME,
+		                      &next, NULL);
+		if (ret != 0 && ret != EINTR) {
+			errno = ret;
+			die("clock_nanosleep:");
+		}
 
-			if (wait.tv_sec >= 0 &&
-			    nanosleep(&wait, NULL) < 0 &&
-			    errno != EINTR)
-					die("nanosleep:");
+		/* Advance the deadline only if it has actually elapsed.
+		 * When woken early by a signal (e.g. SIGUSR1 force
+		 * refresh), keep `next` so the next iteration sleeps
+		 * out the remainder and the wall-clock phase is not
+		 * lost. If we have fallen far behind (suspend/resume
+		 * or a forwards NTP step), snap to the next aligned
+		 * future tick instead of firing a burst of catch-up
+		 * renders. */
+		if (clock_gettime(CLOCK_REALTIME, &now) == 0 &&
+		    (now.tv_sec > next.tv_sec ||
+		     (now.tv_sec == next.tv_sec &&
+		      now.tv_nsec >= next.tv_nsec))) {
+			next.tv_sec  += interval / 1000;
+			next.tv_nsec += (interval % 1000) * 1000000L;
+			if (next.tv_nsec >= 1000000000L) {
+				next.tv_sec  += 1;
+				next.tv_nsec -= 1000000000L;
+			}
+			if (now.tv_sec > next.tv_sec ||
+			    (now.tv_sec == next.tv_sec &&
+			     now.tv_nsec > next.tv_nsec)) {
+				next = now;
+				next.tv_nsec = 0;
+				next.tv_sec += 1;
+			}
 		}
-	} while (!done);
+	}
 
 	if (!sflag) {
 		XStoreName(dpy, DefaultRootWindow(dpy), NULL);
-- 
2.47.3
Received on Mon May 04 2026 - 13:44:51 CEST

This archive was generated by hypermail 2.3.0 : Mon May 04 2026 - 13:48:39 CEST