A gotcha with Python and Unix signals

October 22, 2005

Python likes to handle things through exceptions. As part of this, on Unix it does two important signal changes; it ignores SIGPIPE and catches SIGINT. Each of these can make a Python program report apparent errors where a normal Unix command-line program would just silently exit.

This matters if you want to write a Python program that will play well in an ordinary command-line environment, alongside things like cat, sed, and awk.

First, people expect that they can ^C a Unix command-line program and have it just quietly stop. Python's default behavior turns this into a KeyboardInterrupt exception, which your program is probably not catching; the user will get a multi-line traceback.

Second and more important, Python ignoring SIGPIPE means that your program will get an OSError exception if it writes to a pipe that has closed. Pipes close all the time in Unix command pipelines when you write things like:

generate | mangle.py | head -10

Since head exits after it's read and printed ten lines, further output from mangle.py is probably going to get an OSError. If you didn't handle it (do you guard print statements with trys?), the person running this will see a traceback on standard error. People tend to get irritated when their clean output is messed up with 'error' messages.

(head is not the only program that will do this, and it doesn't necessarily happen all the time. Consider what happens when you feed the output to a pager and quit after seeing the first screen.)

The technique I use for this is:

from signal import signal, \
  SIGPIPE, SIGINT, SIG_DFL, \
  default_int_handler

signal(SIGPIPE, SIG_DFL)
s = signal(SIGINT, SIG_DFL)
if s != default_int_handler:
  signal(SIGINT, s)

Checking what SIGINT is set to is necessary because when your Python program is being run via nohup and similar things, SIGINT will be set to SIG_IGN. If we always set SIGINT to SIG_DFL, we would defeat nohup and irritate the user even more.

(This little thing with SIGINT is not unique to Python; it's something you should watch out for in any program where you're setting a SIGINT handler explicitly. Python itself does it the right way on startup, leaving a SIG_IGN setting alone.)


Comments on this page:

By DanielMartin at 2006-06-28 21:46:53:

Note about that SIGINT handling, and how to not mess up nohup: java (at least on Solaris) doesn't pay attention to what SIGINT was set to before, and it's very annoying that starting a java program in the background with nohup and then running something else and hitting Ctrl-C to get out of that something else will cause the java program to exit!

For extra fun, java also re-enables the SIGHUP handler, no matter what. This means that we got to track down the puzzling case that when you started this perl script in the background with nohup, the java command that the perl script called would fail halfway through for no discernable reason right around the time everyone was getting ready to go home. The reason was that the user who'd started it had logged out for the day (the java program is very long-running). For extra fun, everything was fine if the user managed to start the perl script and logout before the script got to the point of executing the java program.

The fix to this involved modifying our script to run the java program not through a simple system call but through a routine that did the fork and exec itself, and did a setsid in the child before the exec call.

Yuck. "irritate the user" indeed.

Written on 22 October 2005.
« How ETags and If-Modified-Since headers interact
Weekly spam summary on October 22nd, 2005 »

Page tools: View Source, View Normal.
Search:
Login: Password:

Last modified: Sat Oct 22 02:06:04 2005
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.