0

I'm working on a test automation system and I'm coming up with misbehaving programs. With the first one I'm already encountering some unexpected behavior.

    trap "echo No thanks" INT                            

    echo Let me just chill for $1 sec  
    sleep $1                                     
    echo All finished

Observed behavior:

  • sending SIGINT causes "No thanks" to be printed, the sleep is apparently interrupted immediately and "All finished" is also printed immediately after that.
  • behavior is same whether signal is sent separately or performed with keyboard ctrl+c.
  • same behavior is observed if sleep is backgrounded and we wait for it.

Expected behavior:

  • sending SIGINT to the process should result in "No thanks" to be printed for as long as the sleep runs, and then "All finished" will be printed before exiting, after the sleep finishes.
  • If the sleep is backgrounded, issuing keyboard ctrl+c should send SIGINT to the process group, which would include the sleep, so that should stop it prematurely. I am not sure what to expect

Questions:

  • How can I obtain desired behavior?
  • Why exactly does it behave like this (different from my expectation)?

The question is essentially a dupe of this but there are no satisfactory explanations in that answer.

Steven Lu
  • 41,389
  • 58
  • 210
  • 364
  • Did you try `echo "No thanks" && exit`? You can also call a function that would echo and at the end. Sometimes you want your program to go on when it catches a signal (e.g. display statistics on signal `USR1`). – Matthieu Feb 05 '20 at 17:29
  • @Matthieu please re-read the question, the point is to make it not exit, but it *is* exiting unexpectedly – Steven Lu Feb 05 '20 at 17:31
  • Works for me without issue, as you expect. Can you show me how you generate the SIGINT? – pilcrow Feb 05 '20 at 17:45
  • 2
    If I send the signal to the `bash` process alone, I do not see the trap execute until after `sleep` completes. Control-C is different because every process in the group receives the signal directly from the OS. – chepner Feb 05 '20 at 17:45
  • i generate it in another terminal with `kill -SIGINT ` where the pid is the bash process running the script. the behavior is consistently as i describe (and not as i expect) when running that script under zsh or bash. – Steven Lu Feb 05 '20 at 17:48
  • 1
    Your expectation is incorrect — [the trap handler will be run _after_ the foregrounded sleep completes](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_11). Also, maybe you're killing the wrong PID. No one can reproduce your reported behavior, and the answer below is for the Ctrl-C case which you say you're not doing. Can you show us how exactly what you type to know the pid to kill, what you see, and exactly what you type how you kill it? – pilcrow Feb 05 '20 at 17:57
  • Something is strange today because I am pretty sure I was killing the right pid before, but now it is behaving as you report now. I guess I had to have been killing the wrong pid earlier. – Steven Lu Feb 05 '20 at 17:58
  • 1
    Yes, but the problem is, that control+c kills the control group. So `sleep` receives `SIGINT` too. Because `sleep` receives `SIGINT`, it terminates, and then because bash received SIGINT too, it runs the handler. – KamilCuk Feb 05 '20 at 17:58

1 Answers1

2

Currently:

  1. bash waits for sleep to exit
  2. bash and sleep receive sigint
  3. sleep dies
  4. bash finishes waiting and runs the trap

This prevents your desired behavior because:

  • You didn't want sleep to die
  • You didn't want bash to wait for the command to complete before you run the trap

To fix this, you can have sleep ignore the sigint, and have bash run wait in a loop so that the main script gets back control after the ctrl-c, but still waits for the sleep to complete:

trap 'echo "No thanks"' INT
echo "Let me just chill for $1 sec"
# Run sleep in the background
sleep "$1" &
# Loop until we've successfully waited for all processes
until wait; do true; done
echo "All finished"
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • 1
    Traps are not inherited by subshells. Just `sleep $1 &`. `Feel free to remove it and try` - works the same with and without. – KamilCuk Feb 05 '20 at 17:55
  • Och, the difference may be depending if the background process is in the same process group and also receives sigint. – KamilCuk Feb 05 '20 at 18:01
  • 1
    The trap makes the subshell ignore SIGINT, but you don't need to exec in a subshell. `sleep`, when backgrounded, doesn't receive SIGINT (not sure why, but I think backgrounding a job moves it out of the current process group). – chepner Feb 05 '20 at 18:01
  • This code behaves as I desire. it immediately runs the trap upon receiving the signal. Now if only it could be explained why the wait gets interrupted by the signal. I think the explanation is somewhere here: https://www.cons.org/cracauer/sigint.html – Steven Lu Feb 05 '20 at 18:06
  • @StevenLu I'm not sure what kind of explanation you're looking for. `wait` is a bash builtin and you send bash a sigint. – that other guy Feb 05 '20 at 18:08
  • 2
    From https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_11 "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." – anubhava Feb 05 '20 at 18:13