threading.Condition.wait() is not catching SIGTERM

Roy Smith roy at panix.com
Thu Jul 3 16:43:13 EDT 2014


In article <mailman.11469.1404418450.18130.python-list at python.org>,
 Ned Deily <nad at acm.org> wrote:

> In article <17F05A1B-44C8-4F25-AFE9-5DBCFFB9982B at gmail.com>,
> > I have the following code which when executed waits to be interrupted by 
> > SIGINT, SIGTERM or SIGQUIT. When an object is initialized, it creates a 
> > threading.Condition() and acquires() it! The program then registers the 
> > signal handlers where notify() and release() is called when the above 
> > mentioned signals are received. After registering the signal handlers, it 
> > calls wait() on the condition variable and block.
> > 
> > When I tried to stop the program with Ctrl-C, its did not respond. IOW, the 
> > _signal_handler() method did not get called.  
> 
> I'm not sure what you are trying to do but your test case seems flawed.  
> threading.Condition is designed to be used with multiple threads but 
> your test doesn't actually use threads.  If you run your test with a 
> reasonably current Python 3 (after changing print to print()), you can 
> see that it fails (and why it fails) when interrupting with Ctrl-C:
> 
> Waiting to be interrupted!
> ^CReceived terminate request - signal = 2
> Traceback (most recent call last):
>   File "b.py", line 30, in <module>
>     main()
>   File "b.py", line 27, in main 
>     a.register_and_wait()
>   File "b.py", line 22, in register_and_wait
>     self._termination_signal.wait()      # control blocks here!
>   File 
> "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/threadin
> g.py", line 289, in wait
>     waiter.acquire()
>   File "b.py", line 13, in _signal_handler
>     self._termination_signal.notify()
>   File 
> "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/threadin
> g.py", line 339, in notify
>     raise RuntimeError("cannot notify on un-acquired lock")
> RuntimeError: cannot notify on un-acquired lock
> 
> After a quick glance, I'm not sure why Python 2.7 is behaving 
> differently, e.g. not raising an error, since both versions of 
> Condition.notify have the same test so the difference is elsewhere.  
> Feel free to open an issue for not catching the error in 2.7 but you 
> should rethink what you are trying to do here.

That's not the whole story.  I was playing around with his example this 
morning (Python 2.7.1, OSX, Darwin Kernel Version 11.4.2).  My original 
thought was that maybe the wait() call is holding the GIL and breaking 
it up into two threads would solve that somehow.  So, I tried this:

---------------------------------
from signal import signal, SIGINT, SIGTERM, SIGQUIT
from threading import Condition, Thread, current_thread
import time

class A:
    def __init__(self):
        self._termination_signal = Condition()

    def _signal_handler(self, signum, frame):
        print "Received terminate request - signal = {0}".format(signum,)
        del frame
        return

    def register_and_wait(self):
        t = Thread(target=A.run, args=[self._termination_signal])
        t.start()
        signal(SIGINT, self._signal_handler)
        signal(SIGTERM, self._signal_handler)
        signal(SIGQUIT, self._signal_handler)

    @staticmethod
    def run(ts):
        ts.acquire(blocking=0)
        print "Waiting to be interrupted!"
        ts.wait()      # control blocks here!                                                               
        
print "Notified!!"

def main():
    a = A()
    a.register_and_wait()

if __name__ == "__main__":
    main()
---------------------------------------

I got the same result: _signal_handler() never gets called.  You may be 
right that at the point where notify() gets called, you don't have a 
lock, but that's moot because you never even get to that point.  That's 
the fundamental problem here, and it sure smells like a deadlock.  And 
since it happens even in the non-threaded version, my nose says it's 
somehow GIL related.

I just noticed that the acquire call is documented as taking 
specifically True or False, not generically something truthy or falsey.  
I tried it again with accept(blocking=False) and got the same result.

Hmmm, I just also noticed what I think is a bug in the docs 
(https://docs.python.org/2/library/threading.html).  It says, "If a call 
with blocking set to True would block, return False immediately".  Isn't 
that backwards?  Doesn't that describe the blocking=False behavior?



More information about the Python-list mailing list