Search code examples
clinuxtimerzeromq

How to use timerfd properly?


I use timerfd with zmq.

How can I use timerfd_create and timerfd_set to wait one second for the timer (https://man7.org/linux/man-pages/man2/timerfd_create.2.html)?

I have looked through the link but I still do not get how I can initilize a timer that waits one second per tick with create and set. This is exactly my task:

We start a timer with timerfd_create(), which is 1 / sec. ticking. When setting a timer with timer_set_(..) a counter is simply incremented, which is decremented with every tick. When the counter reaches 0, the timer has expired.

In this project we have a function timer _ set _(), where the timer is set with the function timerfd_create and timerfd_settimer(). I hope you can help me.

This is my progress (part of my code):

    struct itimerspec timerValue;

    g_items[n].socket = nullptr; 
    g_items[n].events = ZMQ_POLLIN;

    g_items[n].fd = timerfd_create(CLOCK_REALTIME, 0);
    if(g_items[n].fd == -1 ){
        printf("timerfd_create() failed: errno=%d\n", errno);
        return -1;
    }  

    timerValue.it_value.tv_sec = 1;
    timerValue.it_value.tv_nsec = 0;
    timerValue.it_interval.tv_sec = 1;
    timerValue.it_interval.tv_nsec = 0;

    timerfd_settime(g_items[n].fd,  0, &timerValue, NULL); 

Solution

  • The question appears about setting correctly the timeouts of the timer.

    With the settings

    timerValue.it_value.tv_sec = 1;
    timerValue.it_value.tv_nsec = 0;
    timerValue.it_interval.tv_sec = 1;
    timerValue.it_interval.tv_nsec = 0;
    

    You are correctly setting the initial timeout to 1s (field timerValue.it_value). But you are also setting a periodic interval of 1s, and you didn't mention the will to do it.


    About the timeouts

    This behavior is described by the following passage of the manual:

    int timerfd_create(int clockid, int flags);

    new_value.it_value specifies the initial expiration of the timer, in seconds and nanoseconds. Setting either field of new_value.it_value to a nonzero value arms the timer.
    Setting both fields of new_value.it_value to zero disarms the timer.

    Setting one or both fields of new_value.it_interval to nonzero values specifies the period, in seconds and nanoseconds, for repeated timer expirations after the initial expiration. If both fields of new_value.it_interval are zero, the timer expires just once, at the time specified by new_value.it_value.

    The emphasis on the last paragraph is mine, as it shows what to do in order to have a single-shot timer.


    The benefits of timerrfd. How to detect timer expiration?

    The main advantage provided by timerfd is that the timer is associated to a file descriptor, and this means that it

    may be monitored by select(2), poll(2), and epoll(7).

    The information contained in the other answer about read() is valid as well: let's just say that, even using functions such as select(), read() function will be required in order to consume data in the file descriptor.


    A complete example

    In the following demonstrative program, a timeout of 4 seconds is set; after that a periodic interval of 5 seconds is set.

    The good old select() is used in order to wait for timer expiration, and read() is used to consume data (that is the number of expired timeouts; we will ignore it).

    #include <stdio.h>
    #include <sys/timerfd.h>
    #include <sys/select.h>
    #include <time.h>
    
    int main()
    {
        int tfd = timerfd_create(CLOCK_REALTIME,  0);
        
        printf("Starting at (%d)...\n", (int)time(NULL));
        
        if(tfd > 0)
        {
            char dummybuf[8];
            struct itimerspec spec =
            {
                { 5, 0 }, // Set to {0, 0} if you need a one-shot timer
                { 4, 0 }
            };
            timerfd_settime(tfd, 0, &spec, NULL);
    
            /* Wait */
            fd_set rfds;
            int retval;
    
            /* Watch timefd file descriptor */
            FD_ZERO(&rfds);
            FD_SET(0, &rfds);
            FD_SET(tfd, &rfds);
    
            /* Let's wait for initial timer expiration */
            retval = select(tfd+1, &rfds, NULL, NULL, NULL); /* Last parameter = NULL --> wait forever */
            printf("Expired at %d! (%d) (%d)\n", (int)time(NULL), retval, read(tfd, dummybuf, 8) );
            
            /* Let's wait (twice) for periodic timer expiration */
            retval = select(tfd+1, &rfds, NULL, NULL, NULL);
            printf("Expired at %d! (%d) (%d)\n", (int)time(NULL), retval, read(tfd, dummybuf, 8) );
    
            retval = select(tfd+1, &rfds, NULL, NULL, NULL);
            printf("Expired at %d! (%d) (%d)\n", (int)time(NULL), retval, read(tfd, dummybuf, 8) );
        }
        
        return 0;
    }
    
    

    And here it is the output. Every row contains also the timestamp, so that the actual elapsed time can be checked:

    Starting at (1596547762)...
    Expired at 1596547766! (1) (8)
    Expired at 1596547771! (1) (8)
    Expired at 1596547776! (1) (8)
    

    Please note:

    • We just performed 3 reads, for test
    • The intervals are 4s + 5s + 5s (initial timeout + two interval timeouts)
    • 8 bytes are returned by read(). We ignored them, but they contained the number of the expired timeouts