Tesing of / bugs in new timerfd API

!MAILaRCHIVE_VOTE_RePLACE
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]
To: Davide Libenzi <davidel@...>, Andrew Morton <akpm@...>
Cc: lkml <linux-kernel@...>, <tytso@...>, Thomas Gleixner <tglx@...>, Greg KH <gregkh@...>, Christoph Hellwig <hch@...>, Linux Torvalds <torvalds@...>
Date: Thursday, December 13, 2007 - 10:36 am

Davide, Andrew,

I applied Davide's v3 patchset (sent into LKML on 25 Nov) against
2.4.24-rc3, and did various tests (all on x86).  Several tests
were done using the program at the foot of this mail.  Various others
were done by cobbling together bits of code that I haven't included
here.

In covering the tests I ran, and as a kind of proof of concept,
I'll treat the draft man page as the test spec.

I think I've found two bugs (look for the string "BUG" below), one
of which is a significant deviation from expected behavior.  See
the comments below.

Cheers,

Michael



Verified.  I've tried creating various combinations of
CLOCK_REALTIME and CLOCK_MONOTONIC timers, both one shot
(it_interval is zero) and repeating (it_interval is non-zero),
and everything works as I would expect.


(Verified: see the ERRORS section of the man page, below.)


Disarming a timer works fine in my tests.


Verified: setting both  fields  of  new_value.it_value  to
zero disarms the timer.


I've tested intervals down to 1 nanosec, which seem to seems to
work as I would expect.  (But see the BUG reported below.)


Verified.


Tested: after setting a CLOCK_REALTIME timer with the
TFD_TIMER_ABSTIME flag to expire at some time in the past with
a non-zero interval (e.g., setting 100 seconds in the past, with
a 5 second interval), read() from the file descriptor returns
the correct number of expirations (e.g., 20).

This seems a reasonable thing to do, I suppose.  However, while
playing around to test this, I found what looks like a bug (see
below).

BUG 1:
However, this test exposed what looks like a bug: if I set a
CLOCK_REALTIME clock to expire in the past, with a very small
interval, then the maximum number of expirations that can be
returned via read seems to be limited to 32 bits, even though
we have a 64-bit value for returning this information.
I haven't checked the kernel source to determine where this
bug is.

To demonstrate the bug use the program appended to this mail,
as follows:

# The following starts a CLOCK_REALTIME repeating timer with
# an interval of 1 microsecs (1000 nanosecs), with an initial
# expiration of just under 2^32 nanoseconds in the past.
# The '-a' flags says use TFD_TIMER_ABSTIME for timer_settime() calls.
# The 'r' command reads from the timerfd file descriptor.

$ ./timerfd_test -a -- -4290 0 0 1000
Initial setting for settime:   value=1197543860.000, interval=0.000
./timerfd_test> r           <-- type this immediately
Read: 4292190752            <-- nearly 2^32 expirations, as expected

$ ./timerfd_test -a -- -4290 0 0 1000
Initial setting for settime:   value=1197543900.000, interval=0.000
./timerfd_test> r           <-- type this after 5 secs
Read: 2992244               <-- Looks like the counter rolled over



See the bug described under timerfd_gettime().


BUG 2:
The last sentence does not match the implementation.
(Nor is it consistent with the behavior of POSIX timers.
And I *think* things did work correctly in the original
timerfd() implementation, but I have not gone back to check.)

Suppose that we set an absolute timer to expire 100 seconds
in the future.  Then according to this sentence of the man
page then each subsequent call to timerfd_gettime() should
retrun an itimerspec structure whose it_value steadily
decreases from 100 to 0 (when the timer expires).  (This
is the behavior in the analogous situation with POSIX timers
and with setitimer()/getitimer().)

However, the implementation of timerfd_gettime() always
returns the "time when the timer would next expire", and
this value depends on whether TFD_TIMER_ABSTIME was specified
when setting the timer.

Examples:
$ ./timerfd_test -a -- 100       # -a means TFD_TIMER_ABSTIME
Initial setting for settime:   value=1197550329.140, interval=0.000
./timerfd_test> g                <-- Calls timerfd_gettime()
(elapsed time=  1)
Current value:                 value=1197550329.140, interval=0.000

$ ./timerfd_test -- 100
Initial setting for settime:   value=100.295, interval=0.000
./timerfd_test> g
(elapsed time=  2)
Current value:                 value=18208.440, interval=0.000

In both of the above examples, the "value" retrieved by timer_gettime()
should be a number <= 100.


BUG 2a:
The bug described for timerfd_gettime() also applies for the
'curr_value' returned by a call to timerfd_settime().



Verified: the above was true in all my tests.


Verified.


Verified: each call to timerfd_settime() resets the "expiration
count" to 0, even if a previous setting of the timer had already
expired.


Verified.


Verified.


Verified.


Verified for both poll() and select().


Not verified.  (No easy way to do that from userspace.)


Verified.  Reads from a dup(2)ed file descriptor, or a file
descriptor duplicated via a fork(2) can be used to read
expiration information.


Verified.  A timerfd file descriptor is preserved across an
exceve(), and continues to generate timer expirations that
can be read(2).


Verified.  (And obvious.)


Verified.  (And obvious.)


(CLOCK_MONOTONIC is 1, CLOCK_REALTIME is 0)
Tested: clockid == 2, clockid == -1;
        timerfd_create() fails with EINVAL, as expected.


Currently, 'flags' must be zero.
Tested: flags == 1;
        timerfd_create() fails with EINVAL, as expected.


Not tested.


Not tested.


Not tested.


Not tested.


Tested for timerfd_gettime() and timerfd_settime().
      An invalid file descriptor (i.e., a file descriptor that
      is not open) yields the error EBADF, as expected.


Tested for timerfd_gettime() and timerfd_settime().
      Passing a file descriptor that refers to an object other
      than a timerfd fails with the error EINVAL, as expected.


Tested for timerfd_settime().
     Specifying it_value.tv_nsec == 1000000000 or
     it_interval.tv_nsec == 1000000000 yields the error EINVAL,
     as expected.

--- END MAN PAGE TEXT ---

/* timerfd_test.c */

/* Link with -lrt */

#define _GNU_SOURCE
#include <sys/syscall.h>
#include <unistd.h>
#include <time.h>
#if defined(__i386__)
#define __NR_timerfd_create 322
#define __NR_timerfd_settime 325
#define __NR_timerfd_gettime 326
#endif

static int
timerfd_create(int clockid, int flags)
{
    return syscall(__NR_timerfd_create, clockid, flags);
}

static int
timerfd_settime(int fd, int flags, struct itimerspec *new_value,
        struct itimerspec *curr_value)
{
    return syscall(__NR_timerfd_settime, fd, flags,
            new_value, curr_value);
}

static int
timerfd_gettime(int fd, struct itimerspec *curr_value)
{
    return syscall(__NR_timerfd_gettime, fd, curr_value);
}

#define TFD_TIMER_ABSTIME (1 << 0)


#define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)

// #include <sys/timerfd.h>
#include <time.h>
#include <sys/times.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>     /* Definition of uint64_t */

static void
usage(const char *pname, const char *msg)
{
    if (msg != NULL)
        printf("%s", msg);
    fprintf(stderr, "Usage: %s [options] value-sec [value-nsec "
            "[intvl-sec [intvl-nsec]]]\n", pname);
    fprintf(stderr, "Options are:\n");
    fprintf(stderr, "\t-a    Use absolute timer\n");
    fprintf(stderr, "\t-m    Use CLOCK_MONOTONIC "
            "(instead of CLOCK_REALTIME)\n");

    exit(EXIT_FAILURE);
} /* usage  */

static void
display_help(void)
{
    printf("n val-sec val-nsec intvl-sec intvl-nsec\n"
           "                  Reset timer value & interval\n");
    printf("g                 Get timer value\n");
    printf("r                 Read from file descriptor\n");
    printf("t                 Print elapsed time\n");
} /* display_help */


#define MAX_LINE 1024

static void
print_itimerspec(struct itimerspec *its)
{
    printf("value=%ld.%03ld, interval=%ld.%03ld",
            (long) its->it_value.tv_sec,
            (long) its->it_value.tv_nsec / 1000000,
            (long) its->it_interval.tv_sec,
            (long) its->it_interval.tv_nsec / 1000000);
} /* print_itimerspec */


int
main(int argc, char *argv[])
{
    struct itimerspec new_value, curr_value;
    int fd, flags;
    struct timespec now;
    int s, opt, use_abs_timer, use_monotonic;
    long arg1, arg2, arg3, arg4;
    uint64_t exp;
    time_t start;
    char line[MAX_LINE];
    int num_read, clockid;
    char cmd;

    use_abs_timer = 0;
    use_monotonic = 0;

    while ((opt = getopt(argc, argv, "am")) != -1) {
        switch (opt) {
        case 'a':
            use_abs_timer = 1;
            break;

        case 'm':
            use_monotonic = 1;
            break;

        default:
            usage(argv[1], NULL);
            break;
        } /* switch */
    } /* while */

    clockid = (use_monotonic ? CLOCK_MONOTONIC : CLOCK_REALTIME);

    if (optind + 1 > argc)
        usage(argv[0], NULL);

    if (clock_gettime(clockid, &now) == -1)
        handle_error("clock_gettime");

    flags = use_abs_timer ? TFD_TIMER_ABSTIME : 0;

    /* Create a timer with initial expiration and interval
       as specified in command line */

    if (use_abs_timer) {
        new_value.it_value.tv_sec = now.tv_sec + atoi(argv[optind]);
        new_value.it_value.tv_nsec = (argc > optind + 1) ?
                        atol(argv[optind + 1]) : now.tv_nsec;
    } else {
        new_value.it_value.tv_sec = atoi(argv[optind]);
        new_value.it_value.tv_nsec = (argc > optind + 1) ?
                       atol(argv[optind + 1]) : now.tv_nsec;
    }

    new_value.it_interval.tv_sec = (argc > optind + 2) ?
                        atol(argv[optind + 2]) : 0;
    new_value.it_interval.tv_nsec = (argc > optind + 3) ?
                        atol(argv[optind + 3]) : 0;

    fd = timerfd_create(clockid, 0);
    if (fd == -1)
        handle_error("timerfd_create");

    printf("Initial setting for settime:   ");
    print_itimerspec(&new_value);
    printf("\n");

    s = timerfd_settime(fd, flags, &new_value, &curr_value);
    if (s == -1)
        handle_error("timerfd_settime");

    start = time(NULL);

    for ( ; ; ) {
        printf("%s> ", argv[0]);
        fflush(stdout);

        if (fgets(line, MAX_LINE, stdin) == NULL)       /* EOF */
            exit(EXIT_SUCCESS);
        line[strlen(line) - 1] = '\0';      /* Remove trailing '\n' */

        if (*line == '\0')
            continue;                       /* Skip blank lines */

        if (line[0] == '?')
            display_help();

        num_read = sscanf(line, " %c %ld %ld %ld %ld",
                          &cmd, &arg1, &arg2, &arg3, &arg4);

        switch (cmd) {
        case 'n':
            if (num_read != 5) {
                printf("Wrong number of arguments\n");
                continue;
            }

            if (use_abs_timer) {
                printf("This is an absolute timer\n");
                if (clock_gettime(clockid, &now) == -1)
                    handle_error("clock_gettime");
                printf("Now:                           ");
                printf("value=%ld.%03ld", (long) now.tv_sec,
                        (long) now.tv_nsec / 1000000);
                printf("\n");

                new_value.it_value.tv_sec = now.tv_sec + arg1;
                new_value.it_value.tv_nsec = now.tv_nsec + arg2;

            } else {
                printf("This is a relative timer\n");
                new_value.it_value.tv_sec = arg1;
                new_value.it_value.tv_nsec = arg2;
            }

            new_value.it_interval.tv_sec = arg3;
            new_value.it_interval.tv_nsec = arg4;

            printf("New setting for settime:       ");
            print_itimerspec(&new_value);
            printf("\n");

            s = timerfd_settime(fd, flags, &new_value, &curr_value);
            if (s == -1) {
                perror("timerfd_settime");
                break;
            }
            printf("Previous setting from settime: ");
            print_itimerspec(&curr_value);
            printf("\n");

            break;

        case 't':
            printf("%ld\n", (long) (time(NULL) - start));
            break;

        case 'g':
            s = timerfd_gettime(fd, &curr_value);
            if (s == -1)
                handle_error("timerfd_gettime");
            printf("(elapsed time=%3ld)\n", (long) (time(NULL) - start));
            printf("Current value:                 ");
            print_itimerspec(&curr_value);
            printf("\n");
            break;

        case 'r':
            s = read(fd, &exp, sizeof(uint64_t));
            if (s != sizeof(uint64_t))
                handle_error("read");
            printf("Read: %lld\n", exp);
            break;
        } /* switch */
    } /* for */

    exit(EXIT_SUCCESS);
}


--
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]

Messages in current thread:
Tesing of / bugs in new timerfd API, Michael Kerrisk, (Thu Dec 13, 10:36 am)
Re: Tesing of / bugs in new timerfd API, Davide Libenzi, (Thu Dec 13, 5:49 pm)
Re: Tesing of / bugs in new timerfd API, Michael Kerrisk, (Thu Dec 13, 6:16 pm)
Re: Tesing of / bugs in new timerfd API, Davide Libenzi, (Thu Dec 13, 8:13 pm)
Re: Tesing of / bugs in new timerfd API, Michael Kerrisk, (Fri Dec 14, 7:25 am)
Re: Tesing of / bugs in new timerfd API, Davide Libenzi, (Sun Dec 16, 4:15 pm)