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

From: Arusekk <floss_AT_arusekk.pl>
Date: Mon, 04 May 2026 14:21:37 +0200

Hi,
[response inline]

Dnia 4 maja 2026 13:44:51 CEST, Jialu Xu <xujialu_AT_vimux.org> napisaƂ/a:
>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,

What about bigger backward jumps? I think they would cause a big gap in renders.

>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))) {

This if will likely disable deadline adjustments on backward jumps. Maybe simply always adjust the deadline?

>+ 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);
Received on Mon May 04 2026 - 14:21:37 CEST

This archive was generated by hypermail 2.3.0 : Mon May 04 2026 - 14:24:40 CEST