4

I have a main script that I'm running, and from it I have a second "slow process" I want to kick off, and "do something" in the main script if it doesn't complete in the time limit -- depending on if it completed or not. N.B. If the "slow process" finishes before my time limit, I don't want to have to wait an entire time limit.

I want the "slow process" to keep going so I can gather stats and forensics about it's performance.

I've looked into using timeout, however it will kill my script when finished.

Suppose this simplified example.

main.sh

result=`timeout 3 ./slowprocess.sh`
if [ "$result" = "Complete" ]
then
 echo "Cool it completed, do stuff..."
else
 echo "It didn't complete, do something else..."
fi

slowprocess.sh

#!/bin/bash
start=`date +%s`
sleep 5
end=`date +%s`
total=`expr $end - $start`
echo $total >> /tmp/performance.log
echo "Complete"

Here, it uses timeout -- so the script dies, so nothing winds up in /tmp/performance.log -- I want slowprocess.sh to complete, but, I want main.sh to go onto its next step even if it doesn't finish in the 3 seconds.

8

With ksh/bash/zsh:

{
  (./slowprocess.sh >&3 3>&-; echo "$?") |
    if read -t 3 status; then
      echo "Cool it completed with status $status, do stuff..."
    else
      echo "It didn't complete, do something else..."
    fi
} 3>&1

We duplicate the original stdout onto fd 3 (3>&1) so we can restore it for slowprocess.sh (>&3), while stdout for the rest of the (...) subshell goes to the pipe to read -t 3.

Alternatively, if you want to use timeout (here assuming GNU timeout):

timeout --foreground 3 sh -c './slowprocess.sh;:'

would avoid slowprocess.sh to be killed (the ;: is necessary for sh implementations that optimise by executing the last command in the shell process).

  • Could you add a short explanation of the code? Thanks. – afuna Jan 1 at 10:50
  • @afuna, see edit. – Stéphane Chazelas Jan 1 at 19:24
2

Here's a solution using only ubiquitous shell tools.

This should be easily done by forking the slow process and a sleep in the background and waiting for the first to finish, except that the wait shell builtin waits for all jobs to finish rather than only for the first one.

So instead, fork the slow process and a sleep in the background, have them both report their status through a pipe, and read the first status that comes out of the pipe.

fifo=$(mktemp -u)  # if not on Linux, adapt to what your OS provides
mkfifo -m 600 "$fifo"
{ ./slowprocess.sh; echo z >"$fifo"; } &
sh -c 'sleep 3; echo a' >"$fifo" &
sleep_pgid=$!
read status <$fifo
case $status in
  a) echo "That process is taking a long time"; read ignored <$fifo;;
  z) echo "Done already"; kill -INT -$sleep_pgid;;
esac
rm "$fifo"
  • That mktemp invocation is not portable (won't work on BSDs for instance where you need to specify the template) – Stéphane Chazelas Jul 31 '13 at 6:00
  • pgids are created only in interactive shells. In scripts, you'd need set -m. – Stéphane Chazelas Jul 31 '13 at 6:00
  • With bash, you need quotes around variables in redirected files as well (> "$fifo") – Stéphane Chazelas Jul 31 '13 at 6:01
  • You may want to set the umask to 077 to avoid someone interfering with your fifo. – Stéphane Chazelas Jul 31 '13 at 6:07
  • As a note (probably not an issue to the OP), asynchronous processes have their stdin redirected from /dev/null. – Stéphane Chazelas Jul 31 '13 at 6:11
0

Whatever is blocking in the slowprocess.sh could be backgrounded so that it can continue even after the timeout.

Also you might be able to use a signal from timeout back to your main.sh. See the man page for timeout.

excerpt from timeout info page

`-s SIGNAL'
`--signal=SIGNAL'
     Send this SIGNAL to COMMAND on timeout, rather than the default
     `TERM' signal. SIGNAL may be a name like `HUP' or a number. Also
     see *Note Signal specifications::.

You'd have to trap for this signal inside of main.sh.

Also timeout returns different statuses for different types of failures. You might be able to make use of these as well:

   Exit status:

     124 if COMMAND times out
     125 if `timeout' itself fails
     126 if COMMAND is found but cannot be invoked
     127 if COMMAND cannot be found
     the exit status of COMMAND otherwise

These are present in the variable $?, after the timeout ... line runs.

  • I don't think that can work. If the slow process is backgrounded then timeout won't see whether it's still running. If timeout doesn't kill the process, then it doesn't return. – Gilles Jul 30 '13 at 23:21
  • @Gilles - yeah it was a loosely thought out collection of "possible" solutions, I didn't have the time to provide full solutions, so I just provided some ideas, but I agree they weren't very well vetted, and probably won't end up panning out. My initial thought was that what he wanted wasn't possible. Felt a bit like the OP wanted to have his/her cake and eat it too. – slm Jul 30 '13 at 23:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.