The Improved nano_delay() Function (I Hope)

Last week’s Lesson explored using the clock_gettime() function to pause program execution for a given number of nanoseconds. The code, however, contains a flaw that may render an inaccurate result.

I’m not even going to get into the merits of a nanosecond delay feature when the program itself runs faster than the nanosecond clock. Still, this Lesson is just an exercise that hopefully makes a programmer think and write better code.

In the original nano_delay() function, the delay is calculated as the current nanosecond value plus the delay:

end = res.tv_nsec + ns;

Variable end is the sum of the current nanosecond value (res.tv_nsec) plus the ns argument, or delay in nanoseconds. The value of end determines when the pause is over. For example if the nanosecond delay (value of ns) is 1000 and the current nanosecond value (res.tv_nsec) is 456789, the function waits until the current nanosecond value becomes greater than 456789 + 1000, or 457789.

Suppose that the value of res.tv_nsec is 750,000,000 (commas added for readability). And the delay (ns) is 500,000,000, the value of variable end is 1,250,000,000. This value is out of range for res.tv_nsec. The if test in function’s the endless while loop will never be true.

Nanoseconds are billionths of a second. The defined constant MAX_N in the original code sets the maximum value for nanoseconds to 999,999,999 (remembering that the count starts with zero). The maximum value of a signed long integer is 2,147,483,647, well below the MAX_N value. The issue with the overflow isn’t due to the size of the data type, but the number of nanoseconds in a second.

This code shows may approach to deal with this nanosecond overflow situation. It uses a new variable base to track the overflow.

2023_05_27-Lesson.c

#include <stdio.h>
#include <time.h>

#define MAX_N 999999999L

void nano_delay(long ns)
{
    struct timespec res;
    long end,base;

    /* avoid out of range values */
    if( ns<1 || ns>MAX_N )
        return;

    base = 0;
    /* obtain the current value */
    clock_gettime(CLOCK_REALTIME,&res);
    /* calculate end time */
    end = res.tv_nsec + ns;
    if( end>MAX_N )
        base = MAX_N;
    /* wait for the end time */
    while(1)
    {
        clock_gettime(CLOCK_REALTIME,&res);
        if( base )
            res.tv_nsec += base;
        if( res.tv_nsec > end )
            break;
    }
}

int main()
{
    long delay;

    for( delay=10; delay<MAX_N; delay*=10 )
    {
        printf("Delay = %ld\n",delay);
        nano_delay(delay);
    }

    return(0);
}

Variable base is initialized to zero at Line 15: base = 0

At Line 20, an if test determines whether the end value is greater than defined constant MAX_N. If true, the base is established as MAX_N.

In the endless while loop, a second if test is TRUEwhen the value of base isn’t zero. In this case the end time calculation is made based on the value of res.tv_nsec plus base.

The silly thing is that the program still runs the same as far as I can tell. Which means all my effort here might be in vain. Still, it was a good thought experiment on how to deal with overflow when the value is less than the maximum data type size, or one billion in this case.

I’d be interested to see other approaches to handling this situation, or whether it needs to be dealt with at all.

10 thoughts on “The Improved nano_delay() Function (I Hope)

  1. I just ran some tests and, unfortunately, the above ‘nano_delay()’ function doesnʼt seem to handle overflow correctly. The problem, as far as I can make out, is that in case of an overflow, “if( base ) res.tv_nsec += base;” results in MAX_N being added during the first iteration of the waiting loop (which is then exited immediately).

    To verify, just set .tv_nsec to 750000000l and ns to 500000000l after
    clock_gettime(CLOCK_REALTIME,&res);
    res.tv_nsec = 750000000l; /* Overwrite these values… */
    ns = 500000000l; /* just for testing purposes. */

    To sidestep this problem it is, I think, probably easiest to compare the resulting .tv_sec and .tv_nsec values (of the calculated end time) separately… maybe as follows:

    int nano_delay (long ns)
    {
    /* avoid out of range values */
    if (ns < 1 || ns > MAX_N)
    {
    errno = EINVAL;
    return (-1);
    }

    { struct timespec res, end;

    /* obtain the current value */
    clock_gettime (CLOCK_REALTIME, &res);

    /* calculate end time */
    end.tv_sec = res.tv_sec;
    end.tv_nsec = res.tv_nsec + ns;

    if (end.tv_nsec > MAX_N)
    {
    end.tv_sec += end.tv_nsec / (MAX_N + 1);
    end.tv_nsec = end.tv_nsec % (MAX_N + 1);
    }

    /* wait for the end time */
    for (;;)⁽¹⁾
    {
    clock_gettime (CLOCK_REALTIME, &res);

    if (res.tv_sec > end.tv_sec ||
    (res.tv_sec == end.tv_sec && res.tv_nsec >= end.tv_nsec))
    break;
    }

    return (0);
    }
    }

    ⁽¹⁾ ‘while(1)’ results in a “conditional expression is constant” warning with my compiler.

  2. Thank you for pointing out the flaw. It’s a goofy program, but if someone wants to take it seriously, your solution definitely helps.

  3. Just one additional remark, if I may: in case readers of this blog are wondering, if the given end-condition

    (res.tv_sec > end.tv_sec ||
       (res.tv_sec == end.tv_sec && res.tv_nsec >= end.tv_nsec))

    couldnʼt be simplified to

    (res.tv_sec >= end.tv_sec && res.tv_nsec >= end.tv_nsec)

    One could probably do that; however, with this simplification, the test would fail in one instance, where the above condition wonʼt… letʼs, for example, assume the following values:

    res.tv_sec = 6146; res.tv_nsec = 750000000L;
    end.tv_sec = 6147; end.tv_nsec = 500000000L;

    If the computer system in question was *very* busy (and thinking back to Windows 9x and hard disks), it might conceivably be conceivable that the ‘res’ timespec variable could jump to the following values in a single iteration:

    res.tv_sec = 6148; res.tv_nsec = 250000000L;

    If this were the case, then the simplified condition wouldnʼt regard ‘res’ as greater-than or equal to ‘end’, whereas the given condition will.

  4. To compile on Ubuntu I had to add this:
    #define _GNU_SOURCE
    #define _POSIX_C_SOURCE 1999309L
    to use clock_gettime but timespec_get works without. It does the same thing but the arguments are the other way round. ie.
    timespec_get(&res, TIME_UTC);

    I found this on Github,
    https://github.com/solemnwarning/timespec
    various utility functions for timespec including:
    struct timespec timespec_normalise(struct timespec ts)
    which adjusts the seconds if the nanoseconds are outside +- all the nines, although it says:
    >=1,000,000,000 or <=-1,000,000,000
    in the description.

    Linux has a few sleep functions for seconds, microseconds and nanoseconds. Presumably other OSs have equivalents so using them would be the "official" method.

    You can #include and do while(true) if you want. I’ve also seen people use while(1==1). You have a choice:
    silly compiler warning
    silly extra #include
    silly while condition
    Sorry, I don’t know and non-silly solutions 🙂

  5. The last paragraph should say #include stdbool.h but of course with those symbols which browsers won’t display properly. Sorry!

  6. @ChrisWebb
    Those timespec utility functions look real handy, thanks for mentioning them!

    Also, I didnʼt know about timespec_get (and my Debian 11 has no corresponding manual entries). After some digging, I stumbled over the following SO post:
    https://tinyurl.com/mwt286d3

    Turns out this is a C11 library function (mentioned in the ISO/IEC 9899:2011 standard document in section 7.27.2.5) that has been supported by glibc since version 2.2.1 (released on February 6ᵗʰ 2015) and which internally seems resolves to

    timespec_get (&ts, TIME_UTC); ↔ clock_gettime (CLOCK_REALTIME, &ts);

    … with the added benefit, that this functions is also supported under Visual Studio (and seems to have been supported since VS2017).

    @Infinite loop warning
    The only solution I know, is to use an empty for loop:
    for (;;) {
      …
    }
    … this doesnʼt seem to trigger warnings, irrespective of which compiler is used.

  7. @M.Stuympfl Upon reflecting on your comments above, I think we both missed the point: The timespec structure contains two members: seconds and nanoseconds. I think a better solution is to rely upon the seconds value (tv_sec) to determine the rollover. I would code such a program, but I’m feeling really lazy today.

  8. @dgookin
    I donʼt think I can follow… the only way—I can think of—to simplify the above to a check for a single condition would be to store the end-time in nanoseconds:

    uint64_t cur_nsec, end_nsec = res.tv_sec*(MAX_N+1) + res.tv_nsec + ns;
    do {
      clock_gettime (CLOCK_REALTIME, &res);
      cur_nsec = res.tv_sec*(MAX_N+1) + res.tv_nsec;
    } while (cur_nsec < end_nsec);

    If you have an idea on how these checks might be simplified without having to perform a nasty multiplication on each loop iteration, that might lend itself to a lesson next week ☺

  9. You can confirm an overflow, by checking the value of res.tv_sec. If it’s changed (incremented) since the last read, an overflow has occurred.

  10. I did understand that part… what escapes me, however, is how you propose to use detection of this overflow condition to simplify the given “break” logic.

Leave a Reply