dlp
dlp

Reputation: 486

timer_getoverrun() doesn't behave as expected when using sleep()

Here is a program that uses a POSIX per-process timer alongside the sleep() subroutine. The signal sent when the timer expires has been set to SIGUSR1 rather than SIGALRM, since SIGALRM may be used internally by sleep(), but it still doesn't seem to work.

I have run the program using the command line timer-overruns -d 1 -n 10000000 (1 cs interval) so, in theory, we should expect 100 overruns between calls to sigwaitinfo(). However, timer_getoverrun() returns 0.

I have also tried a version using a time-consuming for loop to introduce the delay. In this case, overruns are recorded.

Does anyone know why this happens? I am running a 3.4 Linux kernel.

Program source

/*
 * timer-overruns.c
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>

// Signal to be used for timer expirations
#define TIMER_SIGNAL SIGUSR1

int main(int argc, char **argv) {
    int opt;
    int d = 0;
    int r = 0; // Repeat indefinitely
    struct itimerspec its;
    its.it_interval.tv_sec = 0;
    its.it_interval.tv_nsec = 0;

    // Parse arguments
    while ((opt = getopt(argc, argv, "d:r:s:n:")) != -1) {
        switch (opt) {
        case 'd': // Delay before calling sigwaitinfo()
            d = atoi(optarg);
            break;
        case 'r': // Number of times to call sigwaitinfo()
            r = atoi(optarg);
            break;
        case 's': // Timer interval (seconds)
            its.it_interval.tv_sec = its.it_value.tv_sec = atoi(optarg);
            break;
        case 'n': // Timer interval (nanoseconds)
            its.it_interval.tv_nsec = its.it_value.tv_nsec = atoi(optarg);
            break;
        default: /* '?' */
            fprintf(stderr,
                    "Usage: %s [-d signal_accept_delay] [-r repetitions] [-s interval_seconds] [-n interval_nanoseconds]\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }
    }

    // Check sanity of command line arguments
    short e = 0;

    if (d < 0) {
        fprintf(stderr, "Delay (-d) cannot be negative!\n");
        e++;
    }
    if (r < 0) {
        fprintf(stderr, "Number of repetitions (-r) cannot be negative!\n");
        e++;
    }
    if (its.it_interval.tv_sec < 0) {
        fprintf(stderr, "Interval seconds value (-s) cannot be negative!\n");
        e++;
    }
    if (its.it_interval.tv_nsec < 0) {
        fprintf(stderr, "Interval nanoseconds value (-n) cannot be negative!\n");
        e++;
    }
    if (its.it_interval.tv_nsec > 999999999) {
        fprintf(stderr, "Interval nanoseconds value (-n) must be < 1 second.\n");
        e++;
    }

    if (e > 0)
        exit(EXIT_FAILURE);

    // Set default values if not specified
    if (its.it_interval.tv_sec == 0 && its.it_interval.tv_nsec == 0) {
        its.it_interval.tv_sec = its.it_value.tv_sec = 1;
        its.it_value.tv_nsec = 0;
    }

    printf("Running with timer delay %d.%09d seconds\n",
            (int) its.it_interval.tv_sec, (int) its.it_interval.tv_nsec);

    // Will be waiting for signals synchronously, so block the one in use.  
    sigset_t sigset;

    sigemptyset(&sigset);
    sigaddset(&sigset, TIMER_SIGNAL);
    sigprocmask(SIG_BLOCK, &sigset, NULL );

    // Create and arm the timer
    struct sigevent sev;
    timer_t timer;

    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = TIMER_SIGNAL;
    sev.sigev_value.sival_ptr = timer;

    timer_create(CLOCK_REALTIME, &sev, &timer);
    timer_settime(timer, TIMER_ABSTIME, &its, NULL );

    // Signal handling loop
    int overruns;
    siginfo_t si;

    // Make the loop infinite if r = 0
    if (r == 0)
        r = -1;

    while (r != 0) {
        // Sleeping should cause overruns
        if (d > 0)
            sleep(d);

        sigwaitinfo(&sigset, &si);

        // Check that the signal is from the timer
        if (si.si_code != SI_TIMER)
            continue;

        overruns = timer_getoverrun(timer);
        if (overruns > 0) {
            printf("Timer overrun occurred for %d expirations.\n", overruns);
        }

        // Decrement r if not repeating indefinitely
        if (r > 0)
            r--;
    }

    return EXIT_SUCCESS;
}

Upvotes: 2

Views: 944

Answers (1)

Nominal Animal
Nominal Animal

Reputation: 39386

You fail to set its.it_value.tv_sec when only nanoseconds are set, i.e. -n 10000000 but no -s 0. Properly initialize its.it_value, or use -s 0 parameter, and your code should work -- after a fashion.

Due to sigemptyset(&sigset); sigaddset(&sigset, TIMER_SIGNAL); sigprocmask(SIG_BLOCK, &sigset, NULL); I do not think there is a race condition, sigwaitinfo() should reliably obtain the pending signal. Check the sigwaitinfo() man page for details; it does describe this very same use case in the Notes section.

It seems that the value returned by timer_getoverrun() the first time after the timer fires, is bogus, at least on a 3.5.0 kernel on an x86-64. In other words, adding this just before the loop,

    printf("Overruns: %d\n",
           timer_getoverrun(timer));
    sigwaitinfo(&sigset, &si);
    printf("Overruns: %d (%d)\n",
           timer_getoverrun(timer), si.si_overrun);
    sigwaitinfo(&sigset, &si);
    printf("Overruns: %d (%d)\n",
           timer_getoverrun(timer), si.si_overrun);

will output zero, a seemingly random overrun count, then the expected overrun count. (Both timer and siginfo overrun counts do match.) I suspect there is a problem in kernel/posix-timers.c, but I might be wrong.. the problem seems to be that the first timer event will use a random value for the previous overrun count. I think I'll investigate that further.

I would appreciate it a lot if you could tell me whether you see the same on your kernel. (Please also state the exact version and architecture. I'm running Ubuntu linux-image-3.5.0-3-generic, version 3.5.0-3.3, on amd64 (x86-64).

Upvotes: 1

Related Questions