diff --git a/src/lib/libast/features/tvlib b/src/lib/libast/features/tvlib index 9b2f64fe2..1286fd241 100644 --- a/src/lib/libast/features/tvlib +++ b/src/lib/libast/features/tvlib @@ -43,6 +43,38 @@ lib utimets link{ } }end +tst prefer_poll note{ is select less precise than 1 ms }end execute{ + #include + #include + #if _sys_select + # include + #else + # include + #endif + int + main() + { + struct timeval tvSleep = { 0, 1 }, tvBefore, tvAfter; + int i; + /* Take a few measurements to prevent a fluke. */ + for (i = 0; i < 5; ++i) + { + gettimeofday(&tvBefore, NULL); + select(0, NULL, NULL, NULL, &tvSleep); + gettimeofday(&tvAfter, NULL); + if (tvAfter.tv_sec == tvBefore.tv_sec + 1) + { + --tvAfter.tv_sec; + tvAfter.tv_usec += 1000000; + } + if ((tvAfter.tv_sec == tvBefore.tv_sec) && + tvAfter.tv_usec - tvBefore.tv_usec < 1000) + return -1; + } + return 0; + } +}end + tst - -DN=1 - -DN=2 - -DN=3 - -DN=4 output{ #include #include diff --git a/src/lib/libast/tm/tvsleep.c b/src/lib/libast/tm/tvsleep.c index 5f65c3b52..8f56b7e45 100644 --- a/src/lib/libast/tm/tvsleep.c +++ b/src/lib/libast/tm/tvsleep.c @@ -21,14 +21,15 @@ ***********************************************************************/ #pragma prototyped +#include #include #include #include #include "FEATURE/tvlib" -#if !_lib_nanosleep && !_lib_usleep -# if _lib_select +#if !_lib_nanosleep +# if _lib_select && !_prefer_poll # if _sys_select # include # else @@ -41,7 +42,8 @@ # endif #endif -#define NANOSECONDS 1000000000L +#define NANOSECONDS 1000000000L +#define MILLISECONDS 1000 /* * sleep for tv @@ -54,16 +56,21 @@ int tvsleep(register const Tv_t* tv, register Tv_t* rv) { + assert(tv); /* Validate argument */ - int r; + /* Return immediately if asked to sleep for no duration. */ + if (!tv->tv_sec && !tv->tv_nsec) + return 0; + + { #if _lib_nanosleep - struct timespec stv; + /* PRECISION: nanoseconds */ + struct timespec stv = { tv->tv_sec, tv->tv_nsec }; struct timespec srv; + int r; - stv.tv_sec = tv->tv_sec; - stv.tv_nsec = tv->tv_nsec; if ((r = nanosleep(&stv, &srv)) && errno == EINTR && rv) { rv->tv_sec = srv.tv_sec; @@ -71,20 +78,45 @@ tvsleep(register const Tv_t* tv, register Tv_t* rv) } return r; + } + #else - Tv_t bv; + Tv_t tvBefore; - tvgettime(&bv); + tvgettime(&tvBefore); { -#if _lib_select +#if _lib_select && !_prefer_poll + /* PRECISION: microseconds */ struct timeval tvSleep = { tv->tv_sec, tv->tv_nsec / 1000 }; if (tv->tv_nsec % 1000) ++tvSleep.tv_usec; + (void)select(0, NiL, NiL, NiL, &tvSleep); - r = select(0, NiL, NiL, NiL, &tvSleep); +#elif _lib_poll + + /* PRECISION: milliseconds + * + * We can sleep for up to 24 days with a single call to poll + * on systems with 32-bit integers, so calling sleep first + * only worsens precision with little gain. For example, the + * UnixWare manual page for usleep warns that a single call of + * that function requires eight system calls, but poll only + * requires one. + */ + struct pollfd dummy; + int timeout = INT_MAX; /* expose bugs */ + + if (tv->tv_sec <= (INT_MAX / MILLISECONDS)) + { + timeout = tv->tv_sec * MILLISECONDS + + tv->tv_nsec / 1000000; + if (timeout < INT_MAX && tv->tv_nsec % 1000000) + ++timeout; + } + (void)poll(&dummy, 0, timeout); #else @@ -156,59 +188,71 @@ tvsleep(register const Tv_t* tv, register Tv_t* rv) } } -#elif _lib_poll - - struct pollfd pfd; - int t; - - if (!(t = (n + 999999L) / 1000000L)) - t = 1; - r = poll(&pfd, 0, t); - #endif - #endif } -/* Ascertain whether we actually slept for a sufficient time. - * It is preferable to sleep a little more than necessary than too little. +/* Unfortunately, some operating systems return success for select + * or poll without having slept for the specified duration, so check + * the clock. + * + * Although time discrepancies when sleeping are inevitable, tvsleep + * can guarantee that they always lie on or after the time specified, + * which is much more useful from the point of view of predictability + * than if they could also occur before. */ { - struct timespec tsAfter; - long sec, nsec; + Tv_t tvAfter; - tvgettime(&tsAfter); + tvgettime(&tvAfter); - /* Calculate seconds left to sleep */ - sec = (long)(tv->tv_sec + tv->tv_nsec / NANOSECONDS) - - ((long)tsAfter.tv_sec - (long)bv.tv_sec); - - /* Calculate nanoseconds left to sleep */ - nsec = (long)(tv->tv_nsec % NANOSECONDS) - - ((long)tsAfter.tv_nsec - (long)bv.tv_nsec); - if (nsec >= NANOSECONDS) + if (tvAfter.tv_nsec < tvBefore.tv_nsec) { - ++sec; - nsec -= NANOSECONDS; + if (!tvAfter.tv_sec) + return 0; + --tvAfter.tv_sec; + tvAfter.tv_nsec += (NANOSECONDS - tvBefore.tv_nsec); } - else if (nsec < 0) + else { - --sec; - nsec += NANOSECONDS; + tvAfter.tv_nsec -= tvBefore.tv_nsec; } + if (tvAfter.tv_sec < tvBefore.tv_sec) + return 0; + tvAfter.tv_sec -= tvBefore.tv_sec; + /* tvAfter now holds the non-negative time slept */ - if (sec >= 0 && (sec > 0 || nsec > 0)) + tvBefore = *tv; + /* Normalize the time to sleep so that ns < 10e9 */ + tvBefore.tv_sec += tvBefore.tv_nsec / NANOSECONDS; + tvBefore.tv_nsec %= NANOSECONDS; + + if (tvBefore.tv_nsec < tvAfter.tv_nsec) + { + if (!tvBefore.tv_sec) + return 0; + --tvBefore.tv_sec; + tvBefore.tv_nsec += (NANOSECONDS - tvAfter.tv_nsec); + } + else + { + tvBefore.tv_nsec -= tvAfter.tv_nsec; + } + if (tvBefore.tv_sec < tvAfter.tv_sec) + return 0; + tvBefore.tv_sec -= tvAfter.tv_sec; + + if (tvBefore.tv_sec > 0 || tvBefore.tv_nsec > 0) { if (rv) - { - rv->tv_sec = sec; - rv->tv_nsec = nsec; - } + *rv = tvBefore; return -1; } } - return r; + } + + return 0; #endif