Win32 trouble with threading, signals, and sleep()

Tim Golden mail at timgolden.me.uk
Sat Aug 9 18:34:51 EDT 2008


Lowell Alleman wrote:
> I'm running into this issue on Windows with the follow exception at
> the time when the signal handler is called:
> 
> Traceback (most recent call last):
>    ...
>     self.done.wait(30)
>   File "D:\Python24\lib\threading.py", line 348, in wait
>     self.__cond.wait(timeout)
>   File "D:\Python24\lib\threading.py", line 222, in wait
>     _sleep(delay)
>     IOError: [Errno 4] Interrupted function call


Well you've certainly picked a ticklish area to run
into problems with ;). First, forget about the
threading aspects for the moment. AFAICT the smallest
program which reproduces your problem is:

<code>
import signal
import time

def handler (*args):
   pass

signal.signal(signal.SIGBREAK, handler)
time.sleep (10)

</code>

Now run that and do a ctrl-break somewhere in
that time.sleep. Sure enough...

<output>
Traceback (most recent call last):
   File "C:\data\temp\sig3.py", line 8, in <module>
     time.sleep (10)
IOError: [Errno 4] Interrupted function call

</output>

Under the covers, the sleep function is implemented
as a WaitForSingleObject call with a timeout. The
object being waited on is an anonymous event which
is set from a console ctrl handler when one of
those interrupts happens (ctrl-c / ctrl-break /
shutdown). This makes sure that the (otherwise
blocking) sleep can be interrupted. Something
like this:

<code>
import win32api
import win32event

hEvent = win32event.CreateEvent (None, 1, 0, None)
def handler (*args):
   win32event.PulseEvent (hEvent)
   return True

win32api.SetConsoleCtrlHandler (handler, 1)
#
# This line is basically the sleep happening
#
win32event.WaitForSingleObject (hEvent, 10000)

</code>

For reasons which I'm not aware of, the code
recognises that the interrupt has fired and
sets the WSAEINTR error code, which is the
IOError 4 which you're seeing, and then exits.
EINTR is usually issued when a blocking call
is cancelled explicitly, so presumably this
is considered a simulation of that. Not sure.

What happens now is that control passes
back out to the routine which called the
sleep. But... an exception condition has
been set inside the sleep call and is now
raised, appearing to come from within the
routine which called sleep.

Clear?

You could obviously continue to catch the
exception. Alternatively,
I think your best bet, as long as you can make
it work with the threads, is to install your
own console ctrl handler. Note that the docs
for this specify that:

"When the signal is received, the system creates
a new thread in the process to execute the function."

So you'll need to be quite careful to make your
code thread-safe. But it should work. I adapted
your example slightly to replace all of the lines
setting the signal handlers to one statement:

win32api.SetConsoleCtrlHandler (handler, 1)

and the handler function itself takes only one
arg, the signal no, and returns False to indicate
that control should pass to the next handler, which
will probably be the default Python handler:

def handler(arg):
     print "Signal handler", arg
     global workers
     for w in workers:
         w.stop()
     return False


Hope that all helps.

TJG




More information about the Python-list mailing list