tags:

views:

1657

answers:

3

The following program is very simple: it outputs a single dot each half a second. If it recieves a SIGQUIT, it proceeds to output ten Qs. If it recieves a SIGTSTP (Ctrl-Z), it outputs ten Zs.

If it recieves a SIGTSTP while printing Qs, it will print ten Zs after it's done with the ten Qs. This is a good thing.

However, if it recieves a SIGQUIT while printing Zs, it fails to print Qs after them. Instead, it prints them out only after I manually terminate execution via a KeyboardInterrupt. I want the Qs to be printed immediately after the Zs.

This happens using Python2.3.

I hope I've made a silly, easily-correctable mistake somewhere. What am I doing wrong? Muchas gracias. ^^

#!/usr/bin/python

from signal import *
from time import sleep
from sys import stdout

def write(text):
    stdout.write(text)
    stdout.flush()

def process_quit(signum, frame):
    for i in range(10):
        write("Q")
        sleep(0.5)

def process_tstp(signum, frame):
    for i in range(10):
        write("Z")
        sleep(0.5)

signal(SIGQUIT, process_quit)
signal(SIGTSTP, process_tstp)

while 1:
    write('.')
    sleep(0.5)
+6  A: 

Your larger problem is blocking in signal handlers.

This is usually discouraged since it can lead to strange timing conditions. But it's not quite the cause of your problem since the timing condition you're vulnerable to exists because of your choice of signal handlers.

Anyway, here's how to at least minimize the timing condition by only setting flags in your handlers and leaving the main while loop to do the actual work. The explanation for why your code is behaving strangely is described after the code.

#!/usr/bin/python

from signal import *
from time import sleep
from sys import stdout

print_Qs = 0
print_Zs = 0

def write(text):
    stdout.write(text)
    stdout.flush()

def process_quit(signum, frame):
     global print_Qs
     print_Qs = 10

def process_tstp(signum, frame):
     global print_Zs
     print_Zs = 10

signal(SIGQUIT, process_quit)
signal(SIGTSTP, process_tstp)

while 1:
    if print_Zs:
        print_Zs -= 1
        c = 'Z'
    elif print_Qs:
        print_Qs -= 1
        c = 'Q'
    else:
        c = '.'
    write(c)
    sleep(0.5)

Anyway, here's what's going on.

SIGTSTP is more special than SIGQUIT.

SIGTSTP masks the other signals from being delivered while its signal handler is running. When the kernel goes to deliver SIGQUIT and sees that SIGTSTP's handler is still running, it simply saves it for later. Once another signal comes through for delivery, such as SIGINT when you CTRL-C (aka KeyboardInterrupt), the kernel remembers that it never delivered SIGQUIT and delivers it now.

You will notice if you change while 1: to for i in range(60): in the main loop and do your test case again, the program will exit without running the SIGTSTP handler since exit doesn't re-trigger the kernel's signal delivery mechanism.

Good luck!

mbac32768
+2  A: 

On Python 2.5.2 on Linux 2.6.24, your code works exactly as you describe your desired results (if a signal is received while still processing a previous signal, the new signal is processed immediately after the first one is finished).

On Python 2.4.4 on Linux 2.6.16, I see the problem behavior you describe.

I don't know whether this is due to a change in Python or in the Linux kernel.

Carl Meyer
+1  A: 

For what it's worth, I was able to reproduce the asker's problem on Python 2.4.3 under Linux 2.6.16.

mbac32768