I l@ve RuBoard Previous Section Next Section

3.7 Signals

For lack of a better analogy, signals are a way to poke a stick at a process. Programs generate signals to trigger a handler for that signal in another process. The operating system pokes too -- some signals are generated on unusual system events and may kill the program if not handled. If this sounds a little like raising exceptions in Python it should; signals are software-generated events, and the cross-process analog of exceptions. Unlike exceptions, though, signals are identified by number, are not stacked, and are really an asynchronous event mechanism controlled by the operating system, outside the scope of the Python interpreter.

In order to make signals available to scripts, Python provides a signal module that allows Python programs to register Python functions as handlers for signal events. This module is available on both Unix-like platforms and Windows (though the Windows version defines fewer kinds of signals to be caught). To illustrate the basic signal interface, the script in Example 3-20 installs a Python handler function for the signal number passed in as a command-line argument.

Example 3-20. PP2E\System\Processes\signal1.py
##########################################################
# catch signals in Python;  pass signal number N as a
# command-line arg, use a "kill -N pid" shell command
# to send this process a signal;  most signal handlers 
# restored by Python after caught (see network scripting
# chapter for SIGCHLD details); signal module avaiable
# on Windows, but defines only a few signal types there; 
##########################################################

import sys, signal, time
def now(  ): return time.ctime(time.time(  ))    # current time string

def onSignal(signum, stackframe):                # python signal handler
    print 'Got signal', signum, 'at', now(  )    # most handlers stay in effect

signum = int(sys.argv[1])
signal.signal(signum, onSignal)                  # install signal handler
while 1: signal.pause(  )                        # wait for signals (or: pass)

There are only two signal module calls at work here:

  • signal.signal takes a signal number and function object, and installs that function to handle that signal number when it is raised. Python automatically restores most signal handlers when signals occur, so there is no need to recall this function within the signal handler itself to re-register the handler. That is, except for SIGCHLD, a signal handler remains installed until explicitly reset (e.g., by setting the handler to SIG_DFL to restore default behavior, or to SIG_IGN to ignore the signal). SIGCHLD behavior is platform-specific.

  • signal.pause makes the process sleep until the next signal is caught. A time.sleep call is similar but doesn't work with signals on my Linux box -- it generates an interrupted system call error. A busy while 1: pass loop here would pause the script too, but may squander CPU resources.

Here is what this script looks like running on Linux: a signal number to watch for (12) is passed in on the command line, and the program is made to run in the background with a & shell operator (available in most Unix-like shells):

[mark@toy]$ python signal1.py 12 &
[1] 809
[mark@toy]$ ps
  PID TTY          TIME CMD
  578 ttyp1    00:00:00 tcsh
  809 ttyp1    00:00:00 python
  810 ttyp1    00:00:00 ps
[mark@toy]$ kill -12 809
[mark@toy]$ Got signal 12 at Fri Sep  8 00:27:01 2000
kill -12 809
[mark@toy]$ Got signal 12 at Fri Sep  8 00:27:03 2000
kill -12 809
[mark@toy]$ Got signal 12 at Fri Sep  8 00:27:04 2000

[mark@toy]$ kill -9 809           # signal 9 always kills the process

Inputs and outputs are a bit jumbled here, because the process prints to the same screen used to type new shell commands. To send the program a signal, the kill shell command takes a signal number and a process ID to be signalled (809); every time a new kill command sends a signal, the process replies with a message generated by a Python signal handler function.

The signal module also exports a signal.alarm function for scheduling a SIGALRM signal to occur at some number of seconds in the future. To trigger and catch timeouts, set the alarm and install a SIGALRM handler as in Example 3-21.

Example 3-21. PP2E\System\Processes\signal2.py
##########################################################
# set and catch alarm timeout signals in Python; 
# time.sleep doesn't play well with alarm (or signal
# in general in my Linux PC), so call signal.pause 
# here to do nothing until a signal is received;
##########################################################

import sys, signal, time
def now(  ): return time.ctime(time.time(  ))

def onSignal(signum, stackframe):                # python signal handler
    print 'Got alarm', signum, 'at', now(  )     # most handlers stay in effect

while 1:
    print 'Setting at', now(  )
    signal.signal(signal.SIGALRM, onSignal)      # install signal handler
    signal.alarm(5)                              # do signal in 5 seconds
    signal.pause(  )                             # wait for signals

Running this script on Linux causes its onSignal handler function to be invoked every five seconds:

[mark@toy]$ python signal2.py
Setting at Fri Sep  8 00:27:53 2000
Got alarm 14 at Fri Sep  8 00:27:58 2000
Setting at Fri Sep  8 00:27:58 2000
Got alarm 14 at Fri Sep  8 00:28:03 2000
Setting at Fri Sep  8 00:28:03 2000
Got alarm 14 at Fri Sep  8 00:28:08 2000
Setting at Fri Sep  8 00:28:08 2000

Generally speaking, signals must be used with cautions not made obvious by the examples we've just seen. For instance, some system calls don't react well to being interrupted by signals, and only the main thread can install signal handlers and respond to signals in a multithreaded program.

When used well, though, signals provide an event-based communication mechanism. They are less powerful than data streams like pipes, but are sufficient in situations where you just need to tell a program that something important has occurred at all, and not pass along any details about the event itself. Signals are sometimes also combined with other IPC tools. For example, an initial signal may inform a program that a client wishes to communicate over a named pipe -- the equivalent of tapping someone's shoulder to get their attention before speaking. Most platforms reserve one or more SIGUSR signal numbers for user-defined events of this sort.

    I l@ve RuBoard Previous Section Next Section