Well that's quite enough about ignoring signals. Now let's install a signal handler.
Now our program is a single-print statement that prints the primes as they're found.
Let's consider this to be a debug print statement, and imagine a situation where we want to turn it off.
The usual way, of course, is to comment the line out. But using a signal, we can turn the feature on
and off at runtime. So we've added a Boolean variable here called debug.
And we've added a test so that the print only happens if debug is true.
Now we define a signal handler. There are a couple of points to notice here.
First of all, it must have this signature. In other words, it must take two arguments.
The first argument is the signal number. This is occasionally useful if you've got one function installed
as the handler for several signal types. Frame references the stat frame at the time of the signal,
and it is unlikely to be of interest unless you're writing a Python debugger.
The global declaration here is necessary to tell the function that references to the variable debug refer
to the global variable defined outside the function. Otherwise, the reference will be taken to mean a
local variable inside the function. Notice also the not operator to flip the Boolean value.
Then we install our handler like this. The second argument is a reference to the handler function that
we've just written. In Bash you may remember, you installed signal handlers with a trap statement.
Now let's run the program. It is presumably finding primes, but it's not printing anything because debug
is currently false. Let's hop over to this other window and send the program a HUP signal.
And you'll see now that the debug printing has started. If we send it another HUP signal, then you'll notice
in the left-hand window that the debug printing stops. Now if any of you have experience in using the
signal-related system calls in Linux, this might be a good moment to point out an important difference in behavior.
Under Linux, handlers are established using the signal system call, GetReset, their default disposition when
the signal occurs. And you need to reestablish the handler if you plan on catching more signals of that type.
Here as we've just seen, you don't need to do that. The signal handler stays in place.
In fact, Python is emulating the older signal handling behavior that was found in BSD UNIX.
Here's one more example. Sometimes it's useful to be able to have a daemon process, which normally runs
silently, to be able to dump out some internal status information on request.
A network server, for example, might show as a list of what client connections it has received.
Here we're using SIGUSR1 to tell our program to show us the number of primes it's found so far.
So here we've defined another signal handler similar to the last one, but this time we just print the length
of the list of primes that we've built up. Notice again the global declaration so that we're referring to
the global primes_list. And here we plug the function in as the handle for SIGUSR1 signals.
Let's run that. Presumably it's busy finding primes. Let's hop over to the other window and send it a SIGUSR1.
And it's found 49 primes so far. Let's wait a moment and then do it again.
And now it's found a slightly bigger number and so on. Let's put it out of its misery by killing it off.
Job done!
For my final example of using a signal, we're going to turn away from our prime number example to see how
to use the alarm function and SIGALRM to implement a timeout. We'll implement a time out on reading a user
response from standard input. More realistically, we might use this technique to force a timeout on
connecting to a remote service or reading from a network connection.
So this function, get_name, basically reads a line intended to be the user's name from standard input and
returns a string. In case the user has gone to sleep, however, it arranges to receive an alarm call.
This call starts a timer, which generates a SIGALRM signal 5 seconds later when the timer expires.
This line installs a handler for SIGALRM. Assuming the user responds before the alarm expires, we call alarm
with an argument of 0, which cancels the alarm, and then we return the string.
Here's the signal handler. Remember, we get here if the user fails to respond within 5 seconds.
We've chosen to handle this situation by raising an exception. We've already seen how to catch exceptions.
Now we're seeing how to throw them. It's not entirely obvious what type of exception to throw.
Maybe we should define a new user asleep exception class. But here we'll settle for throwing one of the
built-in exception types, IOError. To test all of this, we first of all prompt the user.
Since there is no new line at the end here, we need to explicitly flush the stream or the text will just
lurk in the buffer and not show up on the screen. Then we wrap the call to get_name inside a try block.
If it times out, the exception is thrown and caught, and the default name, Sleepy, is assigned.
Let's try running it. First, we'll be quick off the mark and type our name before the 5 seconds are up.
This time we'll just sit back and let it time out. Five seconds later, the alarm fires, the exception is
thrown, and the default name, Sleepy, is assigned.