How to properly set timespec for sem_timedwait to protect against EINVAL error

shafools picture shafools · Aug 12, 2014 · Viewed 8.3k times · Source

I'm trying to use sem_timedwait() to repeatedly lock and unlock a semaphore. Based on the example here, I was setting my struct timespec in the following way for a 20 msec timeout:

sem_t semaphore;  //initialized somewhere else

void waitForSomething ()
{
    int ret;
    struct timespec ts;

    if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
    {
        //throw error
    }
    ts.tv_nsec += 20000000; //timeout of 20 msec 

    while ((ret = sem_timedwait(&semaphore, &ts)) == -1 && errno == EINTR)
        continue;

    if (ret == -1 && errno != ETIMEDOUT) {
        //flag error
    } else {
        //do something
    }

    return;
}

With the above code, my program would consistently fail after running for some time with the EINVAL error code. After debugging I realized that the failure was coming from the fact that ts.tv_nsec exceeds 1000000000 after some time. My current workaround is the following:

sem_t semaphore;  //initialized somewhere else

void waitForSomething ()
{
    int ret;
    struct timespec ts;

    if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
    {
        //throw error
    }
    if (ts.tv_nsec > 979999999) {
        ts.tv_sec += 60;
        ts.tv_nsec = (ts.tv_nsec + 20000000) % 1000000000;
    } else {
        ts.tv_nsec += 20000000; 
    }

    while ((ret = sem_timedwait(&semaphore, &ts)) == -1 && errno == EINTR)
        continue;

    if (ret == -1 && errno != ETIMEDOUT) {
        //throw error
    } else {
        // do something
    }
    return;
}

I'm wondering - is there a better way to do this without having to directly adjust the timespec value yourself?

Answer

Eric Z picture Eric Z · Aug 12, 2014

As far as I know, you need to normalize a timespec structure properly after you add an interval to tv_nsec.

One thing you can do is:

ts.tv_nsec += 20000000;
ts.tv_sec += ts.tv_nsec / 1000000000;
ts.tv_nsec %= 1000000000;

In Linux kernel, you can use set_normalized_timespec() to perform the normalization for you. Refer to here.