Latest working kernel version: none Earliest failing kernel version: all Distribution: all Hardware Environment: all Software Environment: n/a Problem Description: The POSIX.1-2001 specification of timer_getoverrun() says: Only a single signal shall be queued to the process for a given timer at any point in time. When a timer for which a signal is still pending expires, no signal shall be queued, and a timer overrun shall occur. When a timer expiration signal is deliv- ered to or accepted by a process, if the implementation sup- ports the Realtime Signals Extension, the timer_getoverrun() function shall return the timer expiration overrun count for the specified timer. The overrun count returned contains the number of extra timer expirations that occurred between the time the signal was generated (queued) and when it was deliv- ered or accepted, up to but not including an implementation- defined maximum of {DELAYTIMER_MAX}. If the number of such extra expirations is greater than or equal to {DELAYTIMER_MAX}, then the overrun count shall be set to {DELAYTIMER_MAX}. The value returned by timer_getoverrun() shall apply to the most recent expiration signal delivery or acceptance for the timer. If no expiration signal has been delivered for the timer, or if the Realtime Signals Extension is not supported, the return value of timer_getoverrun() is unspecified. A return value of DELAYTIMER_MAX allows an application to know if there were so many timer overruns that the system was unable to measure them. However, the kernel does not support this feature. If the number overruns exceeds the value that can be store in an integer, the counter simply overflows, and cycles again from a low (negative!) value. A suitable value for DELAYTIMER_MAX would of course be INT_MAX. I am not the first to notice this, but previous discussion of the point seemed to peter out with no result: http://thread.gmane.org/gmane.linux.kernel/113276/ Steps to reproduce: Use the program below. The following run, on x86 should show an overrun count of 5e9. Instead, the overrun count is ~7e7: $ ./posix_timers_signal 5 1 Establishing handler for signal 34 Blocking signal 34 timer ID is 0x804c008 Sleeping for 5 seconds Unblocking signal 34 Caught signal 34 sival_ptr = 0xbfd717a4; *sival_ptr = 0x804c008 overrun count = 705355929 /* posix_timers_signal.c Compile on Linux with -lrt */ #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <signal.h> #include <errno.h> #include <time.h> #define CLOCK_ID CLOCK_REALTIME #define SIG (SIGRTMIN) #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) static void print_siginfo(siginfo_t *si) { timer_t *tidp; int or; tidp = si->si_value.sival_ptr; printf(" sival_ptr = %p; ", si->si_value.sival_ptr); printf(" *sival_ptr = 0x%lx\n", (long) *tidp); or = timer_getoverrun(*tidp); if (or == -1) errExit("timer_getoverrun"); else printf(" overrun count = %d\n", or); } static void handler(int sig, siginfo_t *si, void *uc) { printf("Caught signal %d\n", sig); print_siginfo(si); exit(EXIT_SUCCESS); } int main(int argc, char *argv[]) { timer_t timer_id; struct sigevent sev; struct itimerspec its; long long freq_nanosecs; sigset_t mask; struct sigaction sa; if (argc != 3) { fprintf(stderr, "Usage: %s <sleep-secs> <freq-nanosecs>\n", argv[0]); exit(EXIT_FAILURE); } printf("Establishing handler for signal %d\n", SIG); sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = handler; sigemptyset(&sa.sa_mask); if (sigaction(SIG, &sa, NULL) == -1) errExit("sigaction"); printf("Blocking signal %d\n", SIG); sigemptyset(&mask); sigaddset(&mask, SIG); if (sigprocmask(SIG_SETMASK, &mask, NULL) == -1) errExit("sigprocmask"); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIG; sev.sigev_value.sival_ptr = &timer_id; if (timer_create(CLOCK_ID, &sev, &timer_id) == -1) errExit("timercreate"); printf("timer ID is 0x%lx\n", (long) timer_id); freq_nanosecs = atoll(argv[2]); its.it_value.tv_sec = freq_nanosecs / 1000000000; its.it_value.tv_nsec = freq_nanosecs % 1000000000; its.it_interval.tv_sec = its.it_value.tv_sec; its.it_interval.tv_nsec = its.it_value.tv_nsec; if (timer_settime(timer_id, 0, &its, NULL) == -1) errExit("timer_settime"); printf("Sleeping for %d seconds\n", atoi(argv[1])); sleep(atoi(argv[1])); printf("Unblocking signal %d\n", SIG); if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1) errExit("sigprocmask"); }
This bug is very old, test against newer kernel to see if it's still a valid bug. Cheers Nick
Just ran this on 3.17-r2. It produces a similar result: frans@bugger 12665 % ./posix_timers_signal 5 1 Establishing handler for signal 34 Blocking signal 34 timer ID is 0x9614008 Sleeping for 5 seconds Unblocking signal 34 Caught signal 34 sival_ptr = 0xbf89d4c4; *sival_ptr = 0x9614008 overrun count = 705233439
Eventually fixed by: commit 78c9c4dfbf8c04883941445a195276bb4bb92c76 Author: Thomas Gleixner <tglx@linutronix.de> Date: Tue Jun 26 15:21:32 2018 +0200 posix-timers: Sanitize overrun handling
Now, the return value from timer_getoverrun() is clamped to MAX_INT $ uname -r 5.8.16-300.fc33.x86_64 $ ./posix_timers_signal 5 1 Establishing handler for signal 34 Blocking signal 34 timer ID is 0xd536b0 Sleeping for 5 seconds Unblocking signal 34 Caught signal 34 sival_ptr = 0x7fff162dd270; *sival_ptr = 0xd536b0 overrun count = 2147483647
I've revised the manual page as below, and am closing this bug. BUGS POSIX.1 specifies that if the timer overrun count is equal to or greater than an implementation-defined maximum, DELAYTIMER_MAX, then timer_getoverrun() should return DELAYTIMER_MAX. However, before Linux 4.19, if the timer overrun value exceeds the maximum representable integer, the counter cycles, starting once more from low values. Since Linux 4.19, timer_getoverrun() returns DELAY‐ TIMER_MAX (defined as INT_MAX in <limits.h>) in this case (and the overrun value is reset to 0).