From e2d54b716922d68452355a6f905c520ed7116ad7 Mon Sep 17 00:00:00 2001 From: Lev Kujawski <77709387+lev105@users.noreply.github.com> Date: Sun, 14 Feb 2021 00:27:04 -0700 Subject: [PATCH] sleep: guarantee sleeping specified time at minimum (#174) With this patch, the Korn shell can now guarantee that calls to sleep on systems using the select or poll method always result in the system clock advancing by that much time, assuming no interruptions. This compensates for deficiencies in certain systems, including SCO UnixWare. Discussion: https://github.com/ksh93/ksh/pull/174 src/lib/libast/tm/tvsleep.c: - Ensure that at least the time requested to sleep has elapsed for the select and poll methods. - Simplify the logic of calculating the time remaining to sleep and handle the case of an argument of greater than 10e9 nanoseconds being passed to tvsleep. src/cmd/ksh93/bltins/sleep.c: - Eliminate the check for EINTR to handle other cases wherein we have not slept enough. src/cmd/ksh93/tests/variables.sh: - Improve the diagnostic message when the sleep test fails. - Revise the SECONDS function test to expect that we always sleep for at least the time specified. src/cmd/ksh93/tests/functions.h: - Redirect ps stderr to /dev/null. UnixWare ps prints an error message about not being able to find the controlling terminal when shtests output is piped, but we are only using ps to find the PID. --- src/cmd/ksh93/bltins/sleep.c | 2 +- src/cmd/ksh93/tests/functions.sh | 2 +- src/cmd/ksh93/tests/variables.sh | 17 +++++--- src/lib/libast/tm/tvsleep.c | 74 ++++++++++++++++---------------- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/cmd/ksh93/bltins/sleep.c b/src/cmd/ksh93/bltins/sleep.c index 15df5b1a7..aaf2f128b 100644 --- a/src/cmd/ksh93/bltins/sleep.c +++ b/src/cmd/ksh93/bltins/sleep.c @@ -139,7 +139,7 @@ void sh_delay(double t, int sflag) ts.tv_sec = n; ts.tv_nsec = 1000000000 * (t - (double)n); - while(tvsleep(&ts, &tx) < 0 && errno == EINTR) + while(tvsleep(&ts, &tx) < 0) { if ((shp->trapnote & (SH_SIGSET | SH_SIGTRAP)) || sflag) return; diff --git a/src/cmd/ksh93/tests/functions.sh b/src/cmd/ksh93/tests/functions.sh index 2921f5d16..ecd0b218a 100755 --- a/src/cmd/ksh93/tests/functions.sh +++ b/src/cmd/ksh93/tests/functions.sh @@ -1126,7 +1126,7 @@ function gosleep "$bin_sleep" 1 } x=$( - (sleep .25; pid=; ps | grep sleep | read pid extra; [[ $pid ]] && kill -- "$pid") & + (sleep .25; pid=; ps 2>/dev/null | grep sleep | read pid extra; [[ $pid ]] && kill -- "$pid") & gosleep 2> /dev/null print ok ) diff --git a/src/cmd/ksh93/tests/variables.sh b/src/cmd/ksh93/tests/variables.sh index 5309b04a9..136e4a173 100755 --- a/src/cmd/ksh93/tests/variables.sh +++ b/src/cmd/ksh93/tests/variables.sh @@ -40,11 +40,15 @@ if (( RANDOM==RANDOM || $RANDOM==$RANDOM )) then err_exit RANDOM variable not working fi # SECONDS -let SECONDS=0.0 -sleep .001 -if (( SECONDS < .001 )) -then err_exit "either 'sleep' or \$SECONDS not working" +float secElapsed=0.0 secSleep=0.001 +let SECONDS=$secElapsed +sleep $secSleep +secElapsed=SECONDS +if (( secElapsed < secSleep )) +then err_exit "slept ${secElapsed} seconds instead of ${secSleep}: " \ + "either 'sleep' or \$SECONDS not working" fi +unset -v secElapsed secSleep # _ set abc def if [[ $_ != def ]] @@ -508,12 +512,11 @@ fi function foo { typeset SECONDS=0 - sleep .002 + sleep 0.002 print $SECONDS - } x=$(foo) -(( x >.001 && x < 1 )) +(( x >= 0.002 && x < 1 )) ' } 2> /dev/null || err_exit 'SECONDS not working in function' cat > $tmp/script <<-\! diff --git a/src/lib/libast/tm/tvsleep.c b/src/lib/libast/tm/tvsleep.c index 3a1b9aefe..5f65c3b52 100644 --- a/src/lib/libast/tm/tvsleep.c +++ b/src/lib/libast/tm/tvsleep.c @@ -41,6 +41,8 @@ # endif #endif +#define NANOSECONDS 1000000000L + /* * sleep for tv * non-zero exit if sleep did not complete @@ -53,11 +55,12 @@ int tvsleep(register const Tv_t* tv, register Tv_t* rv) { + int r; + #if _lib_nanosleep struct timespec stv; struct timespec srv; - int r; stv.tv_sec = tv->tv_sec; stv.tv_nsec = tv->tv_nsec; @@ -77,13 +80,11 @@ tvsleep(register const Tv_t* tv, register Tv_t* rv) #if _lib_select - struct timeval stv; + struct timeval tvSleep = { tv->tv_sec, tv->tv_nsec / 1000 }; + if (tv->tv_nsec % 1000) + ++tvSleep.tv_usec; - stv.tv_sec = tv->tv_sec; - if (!(stv.tv_usec = tv->tv_nsec / 1000)) - stv.tv_usec = 1; - if (select(0, NiL, NiL, NiL, &stv) >= 0) - return 0; + r = select(0, NiL, NiL, NiL, &tvSleep); #else @@ -162,51 +163,52 @@ tvsleep(register const Tv_t* tv, register Tv_t* rv) if (!(t = (n + 999999L) / 1000000L)) t = 1; - if (poll(&pfd, 0, t) >= 0) - return 0; + r = poll(&pfd, 0, t); #endif #endif } - bad: - if (errno == EINTR && rv) + +/* Ascertain whether we actually slept for a sufficient time. + * It is preferable to sleep a little more than necessary than too little. + */ { - tvgettime(rv); - if (rv->tv_nsec < bv.tv_nsec) + struct timespec tsAfter; + long sec, nsec; + + tvgettime(&tsAfter); + + /* 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) { - rv->tv_nsec += 1000000000L; - rv->tv_sec--; + ++sec; + nsec -= NANOSECONDS; } - rv->tv_nsec -= bv.tv_nsec; - rv->tv_sec -= bv.tv_sec; - if (rv->tv_sec > tv->tv_sec) + else if (nsec < 0) { - rv->tv_sec = 0; - rv->tv_nsec = 0; + --sec; + nsec += NANOSECONDS; } - else + + if (sec >= 0 && (sec > 0 || nsec > 0)) { - rv->tv_sec = tv->tv_sec - rv->tv_sec; - if (rv->tv_nsec > tv->tv_nsec) + if (rv) { - if (!rv->tv_sec) - { - rv->tv_sec = 0; - rv->tv_nsec = 0; - } - else - { - rv->tv_sec--; - rv->tv_nsec = 1000000000L - rv->tv_nsec + tv->tv_nsec; - } + rv->tv_sec = sec; + rv->tv_nsec = nsec; } - else - rv->tv_nsec = tv->tv_nsec - rv->tv_nsec; + return -1; } } - return -1; + return r; #endif