1

I am trying to implement a timeout for a big script which calls a lot of external process.

I start a separate process as a watchdog that checks every second if the timeout is reached

(

  PARENT_PID=$$

  ((t = 2 ))
  while ((t > 0)); do
    sleep 1
    ((t -= 1))
  done

  kill -s ALRM ${PARENT_PID} 2> /dev/null

) &

And then I trap the ALRM signal in the main script

trap critical ALRM

and finally in the critical function I print out some text and exit the program cleanly

Now the problem

  • if the timeout occurs while $PARENT_PID is running everything works as expected
  • if the timeout occurs while a child process is running (e.g., I call openssl and openssl is running) nothing happens. If I terminate the running child process (e.g. by pressing CRTL+C) the parent process trap is activated and everything continues as expected

How can I fix the problem? So that the trap in the parent process is called and I can terminate the whole?

Update

If the watchdog kills with -KILL the parent process, the process with all the child processed are terminated as expected

The problem is that I would like to intercept the timeout and generate some output. For this reason I use the ALRM signal.

The KILL signal cannot be ignored while the ALRM one can.

My idea at the moment is the following:

  • the parent process has a trap for the alarm
  • the child process ignores the alarm signal
  • when the signal is sent to the parent the child process is running and the parent is in the background
  • my process is waken up when the child is terminated

If this is correct my idea will never work.

To make it work I should for each child process

  • start it in the background
  • wait for it and continue

So that the parent process will be able to trap the signal

Am I correct?

Update 2

A reduced example

critical() {
    printf "whathever\n"
    WATCHDOG_PID=$!
    kill -TERM $WATCHDOG_PID
}

(

    PARENT_PID=$$

    ((t = "${TIMEOUT}" ))
    while ((t > 0)); do
        sleep 1
        ((t -= 1))
    done

    kill -s ALRM ${PARENT_PID} 2> /dev/null

) &

trap critical ALRM

# an example of a long running command
(echo 'Q' | openssl s_client -connect corti.li:imap -servername corti.li -verify 6 ) &
wait

WATCHDOG_PID=$!
kill -TERM $WATCHDOG_PID

If the ALRM signal is sent while openssl is running nothing happens. Even if the openssl is in the background the trap is never called

  • Why not just wrap the parent process with timeout(1) ? – thrig Aug 18 '17 at 13:52
  • I want the solution to be portable and timeout is not installed by default on non-GNU systems (e.g., macOS, BSD, ...) – Matteo Aug 18 '17 at 13:53
  • Is the use of "wait" not a consideration? – Raman Sailopal Aug 18 '17 at 13:55
  • Could you please elaborate: how? (sorry but just "how?" was too short for a comment) – Matteo Aug 18 '17 at 13:56
  • I obviously don't know your use case scenario fully enough but you could spawn the commands from the master script with wait and a timeout, The master script will spawn the child processes, wait for the timeout and then continue. "Wait" is a bash implementation and so if you are using another shell it may not be any good for you. I'm not sure about older versions of Bash either – Raman Sailopal Aug 18 '17 at 14:00
1

A full answer needs the code from your parent, but lacking that, here is a hint of what is probably happening:

Open Group Base Specifications Issue 6 - Section 2.11

When a signal for which a trap has been set is received while the shell is waiting for the completion of a utility executing a foreground command, the trap associated with that signal shall not be executed until after the foreground command has completed.

When the shell is waiting, by means of the wait utility, for asynchronous commands to complete, the reception of a signal for which a trap has been set shall cause the wait utility to return immediately with an exit status >128, immediately after which the trap associated with that signal shall be taken.

(formatting mine)

What that means is that if your parent is waiting with anything but the wait command, signal handling is delayed until the end of whatever it is doing.

It's up to you to make your parent wait with wait, so it can receive your SIGALRM right away.

  • This is what I thought but even if the child (openssl) in my case, is in the background, the parent is not getting the signal – Matteo Aug 18 '17 at 14:38
  • @Matteo> what it the parent doing in the meantime? The wording can be deceptive as, for instance, running sleep 10 in the parent will prevent the signal from being received, sleep 10 being run as a foreground command. – spectras Aug 18 '17 at 14:40
  • See my example: I am running openssl in the background – Matteo Aug 18 '17 at 14:42
  • @Matteo> okay, that's what I was interested in. So normally, the wait command should return immediately upon receiving SIGALRM. Hmmm. – spectras Aug 18 '17 at 14:45
  • @Matteo> running your example (replacing the openssl command with a sleep 86400) on my system gives the expected result. I mean, trap gets executed and wait returns. The sleep command keeps running though. – spectras Aug 18 '17 at 14:51

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.