Bug 12665

Summary: POSIX timers API does not support DELAYTIMER_MAX
Product: Timers Reporter: Michael Kerrisk (mtk.manpages)
Component: Interval TimersAssignee: timers_interval-timers
Status: RESOLVED CODE_FIX    
Severity: normal CC: alan, fransklaver, xerofoify
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: 3.17rc2 Subsystem:
Regression: No Bisected commit-id:

Description Michael Kerrisk 2009-02-08 12:08:51 UTC
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");
}
Comment 1 xerofoify 2014-06-24 16:57:29 UTC
This bug is very old, test against newer kernel to see if it's
still a valid bug.
Cheers Nick
Comment 2 Frans Klaver 2014-08-30 21:39:37 UTC
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
Comment 3 Michael Kerrisk 2020-11-10 23:00:19 UTC
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
Comment 4 Michael Kerrisk 2020-11-11 08:11:55 UTC
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
Comment 5 Michael Kerrisk 2020-11-11 08:46:47 UTC
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).