51

I have been debugging a Python program which segfaults after receiving a KeyboardInterrupt exception. This is normally done by pressing Ctrl+C from the shell. To test if a particular code change fixed the bug, I had a small shell-script that sent SIGINT to the program at random time after start-up. The problem I have is that sending Ctrl+C seems to have a different effect on the program than sending the signal SIGINT and is thus not causing the bug to appear, so I quite wonder what the difference is then between the two actions.

The program does not catch any keyboard actions at all, and is just a python program with some threads/processes in them. It installs no signal handlers (though Python does), and stty -a gives intr = ^C. I suspect it might be that Ctrl+C sends SIGINT to all the sub-processes/threads while kill -INT only sends to the primary process, but that is as far my suspicions go.

Here is the shell script which sends the kill -INT.

wait
while :; do
    seconds="$(python -c 'import random; print random.random()*4')"
    ./mandos --debug --configdir=confdir \
             --statedir=statedir --no-restore --no-dbus &
    pid=$!
    { sleep $seconds; kill -INT $pid; } &
    fg %./mandos
    status=$?
    if [ $status -gt 1 ]; then
        echo "Failed exit $status after $seconds seconds"
        break
    fi
    wait
done
SamB
  • 9,039
  • 5
  • 49
  • 56
Belorn
  • 521
  • 1
  • 4
  • 5
  • I'm not sure how much of a difference this would be but its possible ctrl+c is sending `SIGTERM` instead of `SIGINT`. Also, when handling the exception are you cleaning up your sub-processes/threads correctly? The way python handles threads I don't believe it would seg-fault, but its probably possible with sub-processes. – David Dec 06 '11 at 11:24
  • +C can be configured, so check your `stty -a` settings, look for `intr = ^C`, maybe ^C is set for something else as well? – cdarke Dec 06 '11 at 12:39
  • is there any multithreading in the code? – Casey Dec 06 '11 at 13:10
  • just saw that you said there were threads in your OP. I would say that could be your problem. The threads aren't being terminated correctly when the script receives the SIGINT. – Casey Dec 06 '11 at 13:12
  • Do you mean threads are effected if you do ctrl + C compared to sending the signal -INT? The reason I said threads is that Im using multiprocessing manager, and the way python implement it uses the threading library. – Belorn Dec 06 '11 at 13:39
  • @Belorn : be carefull, despite the interfaces are similar, multiprocessing is not the same than threading. With multiprocess, python creates real processes, not threads. – Cédric Julien Dec 06 '11 at 13:44
  • You could always try to debug where the seg fault is: http://docs.python.org/library/pdb.html If your using eclipse it should be easy to see where it seg faults and to see a stack trace on whats going on. – David Dec 06 '11 at 22:54

2 Answers2

46

^C sends a SIGINT to all the processes in the foreground process group. To do the equivalent with kill, you should send the signal to the process group (OS-level concept):

kill -SIGINT -<pid>

or to the job (shell-level concept, the pipeline ended with &):

kill -SIGINT %
ninjalj
  • 42,493
  • 9
  • 106
  • 148
  • This was what I was looking for, but for unknown reasons the code still do not segfault when I use the automated script. I found the bug causing python to crash - python library multiprocessing is implemented with threads, and it causes segfaults in gobject if one do not call gobject.threads_init. Still, the original question for me is left as a mystery as why the manually invoked ctrl+c triggered the bug, but the automated script did not. – Belorn Mar 10 '12 at 22:27
  • 3
    Sorry for being obtuse, but what does the '%' mean in this context? – Kevin Cantwell Oct 04 '13 at 20:02
  • 1
    @KevinCantwell: it refers to the current job, i.e. the last backgrounded job (either started in the background, or stopped while it was in the foreground, and thus backgrounded). – ninjalj Oct 05 '13 at 08:12
  • @ninjalj Am I correct in assuming that it is the shell that translates `^C` to `SIGINT` and then sends the latter to the processes in the foreground process group? Or does the Python interpreter partake in this process in any way? – balu Dec 22 '20 at 15:43
  • 1
    @balu: SIGINT is handled by the line discipline, see termios(3) VINTR. See also stty(1) for the user level command to change line disciplinr settings. So, the shell just has to configure correctly the line discipline, and set the foreground process group with tcsetpgrp(3). – ninjalj Dec 22 '20 at 16:55
  • 1
    @ninjalj This is the first time I'm hearing of the term [*line discipline*](https://en.wikipedia.org/wiki/Line_discipline), so this was really helpful! Thanks for the pointer! – balu Dec 22 '20 at 17:22
  • 1
    Addendum: I've found https://www.linusakesson.net/programming/tty/ (section "An example") really helpful to understand where the line discipline enters the game on modern desktop systems. – balu Dec 22 '20 at 17:46
9

As described here :

Python installs a small number of signal handlers by default: SIGPIPE is ignored (so write errors on pipes and sockets can be reported as ordinary Python exceptions) and SIGINT is translated into a KeyboardInterrupt exception. All of these can be overridden.

so, the behaviour should be the same between sending a SIGINT and a Ctrl + c.

But, you have to be carefull with the KeyboardInterrupt, if somewhere in your code you've got a

try:
   ...
except:   # notice the lack of exception class
   pass

this will "eat" the KeyboardInterrupt exception.

Cédric Julien
  • 78,516
  • 15
  • 127
  • 132
  • Thanks for the advice. found one line doing that, and I think that will fix an unrelated bug. But even with that commented out, the primary issue with segfault is still there, and the more weird one that sending kill -INT do not trigger it but ctrl + c does. – Belorn Dec 06 '11 at 11:35
  • 3
    I like this answer because it makes clear *where* exactly the translation between SIGINT and `KeyboardInterrupt` happens. As for your warning: For this reason I would always recommend using `try: ... except Exception: ...` as a catch-all for "normal" exceptions, so as to not accidentally catch unusual ones like `KeyboardInterrupt` and `SystemExit`, compare [link 1](https://docs.python.org/3.8/library/exceptions.html#KeyboardInterrupt) and [link 2](https://docs.python.org/3.8/library/exceptions.html#SystemExit). – balu Dec 22 '20 at 15:40