Messages in this thread | | | From | "Michael Kerrisk (man-pages)" <> | Date | Wed, 1 Apr 2020 11:01:18 +0200 | Subject | timer_settime() and ECANCELED |
| |
Hello Thomas, et al,
Following on from our discussion of read() on a timerfd [1], I happened to remember a Debian bug report [2] that points out that timer_settime() can fail with the error ECANCELED, which is both surprising and odd (because despite the error, the timer does get updated).
The relevant kernel code (I think, from your commit [3]) seems to be the following in timerfd_setup():
if (texp != 0) { if (flags & TFD_TIMER_ABSTIME) texp = timens_ktime_to_host(clockid, texp); if (isalarm(ctx)) { if (flags & TFD_TIMER_ABSTIME) alarm_start(&ctx->t.alarm, texp); else alarm_start_relative(&ctx->t.alarm, texp); } else { hrtimer_start(&ctx->t.tmr, texp, htmode); }
if (timerfd_canceled(ctx)) return -ECANCELED; }
Using a small test program [4] shows the behavior. The program loops, repeatedly calling timerfd_settime() (with a delay of a few seconds before each call). In another terminal window, enter the following command a few times:
$ sudo date -s "5 seconds" # Add 5 secs to wall-clock time
I see behavior as follows (the /sudo date -s "5 seconds"/ command was executed before loop iterations 0, 2, and 4):
[[ $ ./timerfd_settime_ECANCELED 0 Current time is 1585729978 secs, 868510078 nsecs Timer value is now 0 secs, 0 nsecs timerfd_settime() succeeded Timer value is now 9 secs, 999991977 nsecs
1 Current time is 1585729982 secs, 716339545 nsecs Timer value is now 6 secs, 152167990 nsecs timerfd_settime() succeeded Timer value is now 9 secs, 999992940 nsecs
2 Current time is 1585729991 secs, 567377831 nsecs Timer value is now 1 secs, 148959376 nsecs timerfd_settime: Operation canceled Timer value is now 9 secs, 999976294 nsecs
3 Current time is 1585729995 secs, 405385503 nsecs Timer value is now 6 secs, 161989917 nsecs timerfd_settime() succeeded Timer value is now 9 secs, 999993317 nsecs
4 Current time is 1585730004 secs, 225036165 nsecs Timer value is now 1 secs, 180346909 nsecs timerfd_settime: Operation canceled Timer value is now 9 secs, 999984345 nsecs ]]
I note from the above.
(1) If the wall-clock is changed before the first timerfd_settime() call, the call succeeds. This is of course expected. (2) If the wall-clock is changed after a timerfd_settime() call, then the next timerfd_settime() call fails with ECANCELED. (3) Even if the timerfd_settime() call fails, the timer is still updated(!).
Some questions: (a) What is the rationale for timerfd_settime() failing with ECANCELED in this case? (Currently, the manual page says nothing about this.) (b) It seems at the least surprising, but more likely a bug, that timerfd_settime() fails with ECANCELED while at the same time successfully updating the timer value.
Your thoughts?
Thanks,
Michael
[1] https://lore.kernel.org/lkml/3cbd0919-c82a-cb21-c10f-0498433ba5d1@gmail.com/
[2] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=947091
[3] commit 99ee5315dac6211e972fa3f23bcc9a0343ff58c4 Author: Thomas Gleixner <tglx@linutronix.de> Date: Wed Apr 27 14:16:42 2011 +0200
timerfd: Allow timers to be cancelled when clock was set
[4] /* timerfd_settime_ECANCELED.c */ #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <inttypes.h> #include <sys/timerfd.h>
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[]) { struct itimerspec ts, gts; struct timespec start;
int tfd = timerfd_create(CLOCK_REALTIME, 0); if (tfd == -1) errExit("timerfd_create");
ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 10;
int flags = TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET;
for (long j ; ; j++) {
/* Inject a delay into each loop, by calling getppid() many times */
for (int k = 0; k < 10000000; k++) getppid();
if (j % 1 == 0) printf("%ld\n", j);
/* Display the current wall-clock time */
if (clock_gettime(CLOCK_REALTIME, &start) == -1) errExit("clock_gettime"); printf("Current time is %ld secs, %ld nsecs\n", start.tv_sec, start.tv_nsec);
/* Before resetting the timer, retrieve its current value so that after the timerfd_settime() call, we can see whether the the value has changed */
if (timerfd_gettime(tfd, >s) == -1) perror("timerfd_gettime"); printf("Timer value is now %ld secs, %ld nsecs\n", gts.it_value.tv_sec, gts.it_value.tv_nsec);
/* Reset the timer to now + 10 secs */
ts.it_value.tv_sec = start.tv_sec + 10; ts.it_value.tv_nsec = start.tv_nsec; if (timerfd_settime(tfd, flags, &ts, NULL) == -1) perror("timerfd_settime"); else printf("timerfd_settime() succeeded\n");
/* Display the timer value once again */
if (timerfd_gettime(tfd, >s) == -1) perror("timerfd_gettime"); printf("Timer value is now %ld secs, %ld nsecs\n", gts.it_value.tv_sec, gts.it_value.tv_nsec);
printf("\n"); } }
| |