66import sys
77from test .support import run_unittest , import_module
88thread = import_module ('_thread' )
9+ import time
910
1011if sys .platform [:3 ] in ('win' , 'os2' ) or sys .platform == 'riscos' :
1112 raise unittest .SkipTest ("Can't test signal on %s" % sys .platform )
@@ -34,12 +35,12 @@ def send_signals():
3435 signalled_all .release ()
3536
3637class ThreadSignals (unittest .TestCase ):
37- """Test signal handling semantics of threads.
38- We spawn a thread, have the thread send two signals, and
39- wait for it to finish. Check that we got both signals
40- and that they were run by the main thread.
41- """
38+
4239 def test_signals (self ):
40+ # Test signal handling semantics of threads.
41+ # We spawn a thread, have the thread send two signals, and
42+ # wait for it to finish. Check that we got both signals
43+ # and that they were run by the main thread.
4344 signalled_all .acquire ()
4445 self .spawnSignallingThread ()
4546 signalled_all .acquire ()
@@ -66,6 +67,115 @@ def test_signals(self):
6667 def spawnSignallingThread (self ):
6768 thread .start_new_thread (send_signals , ())
6869
70+ def alarm_interrupt (self , sig , frame ):
71+ raise KeyboardInterrupt
72+
73+ def test_lock_acquire_interruption (self ):
74+ # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
75+ # in a deadlock.
76+ oldalrm = signal .signal (signal .SIGALRM , self .alarm_interrupt )
77+ try :
78+ lock = thread .allocate_lock ()
79+ lock .acquire ()
80+ signal .alarm (1 )
81+ self .assertRaises (KeyboardInterrupt , lock .acquire )
82+ finally :
83+ signal .signal (signal .SIGALRM , oldalrm )
84+
85+ def test_rlock_acquire_interruption (self ):
86+ # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
87+ # in a deadlock.
88+ oldalrm = signal .signal (signal .SIGALRM , self .alarm_interrupt )
89+ try :
90+ rlock = thread .RLock ()
91+ # For reentrant locks, the initial acquisition must be in another
92+ # thread.
93+ def other_thread ():
94+ rlock .acquire ()
95+ thread .start_new_thread (other_thread , ())
96+ # Wait until we can't acquire it without blocking...
97+ while rlock .acquire (blocking = False ):
98+ rlock .release ()
99+ time .sleep (0.01 )
100+ signal .alarm (1 )
101+ self .assertRaises (KeyboardInterrupt , rlock .acquire )
102+ finally :
103+ signal .signal (signal .SIGALRM , oldalrm )
104+
105+ def acquire_retries_on_intr (self , lock ):
106+ self .sig_recvd = False
107+ def my_handler (signal , frame ):
108+ self .sig_recvd = True
109+ old_handler = signal .signal (signal .SIGUSR1 , my_handler )
110+ try :
111+ def other_thread ():
112+ # Acquire the lock in a non-main thread, so this test works for
113+ # RLocks.
114+ lock .acquire ()
115+ # Wait until the main thread is blocked in the lock acquire, and
116+ # then wake it up with this.
117+ time .sleep (0.5 )
118+ os .kill (process_pid , signal .SIGUSR1 )
119+ # Let the main thread take the interrupt, handle it, and retry
120+ # the lock acquisition. Then we'll let it run.
121+ time .sleep (0.5 )
122+ lock .release ()
123+ thread .start_new_thread (other_thread , ())
124+ # Wait until we can't acquire it without blocking...
125+ while lock .acquire (blocking = False ):
126+ lock .release ()
127+ time .sleep (0.01 )
128+ result = lock .acquire () # Block while we receive a signal.
129+ self .assertTrue (self .sig_recvd )
130+ self .assertTrue (result )
131+ finally :
132+ signal .signal (signal .SIGUSR1 , old_handler )
133+
134+ def test_lock_acquire_retries_on_intr (self ):
135+ self .acquire_retries_on_intr (thread .allocate_lock ())
136+
137+ def test_rlock_acquire_retries_on_intr (self ):
138+ self .acquire_retries_on_intr (thread .RLock ())
139+
140+ def test_interrupted_timed_acquire (self ):
141+ # Test to make sure we recompute lock acquisition timeouts when we
142+ # receive a signal. Check this by repeatedly interrupting a lock
143+ # acquire in the main thread, and make sure that the lock acquire times
144+ # out after the right amount of time.
145+ self .start = None
146+ self .end = None
147+ self .sigs_recvd = 0
148+ done = thread .allocate_lock ()
149+ done .acquire ()
150+ lock = thread .allocate_lock ()
151+ lock .acquire ()
152+ def my_handler (signum , frame ):
153+ self .sigs_recvd += 1
154+ old_handler = signal .signal (signal .SIGUSR1 , my_handler )
155+ try :
156+ def timed_acquire ():
157+ self .start = time .time ()
158+ lock .acquire (timeout = 0.5 )
159+ self .end = time .time ()
160+ def send_signals ():
161+ for _ in range (40 ):
162+ time .sleep (0.05 )
163+ os .kill (process_pid , signal .SIGUSR1 )
164+ done .release ()
165+
166+ # Send the signals from the non-main thread, since the main thread
167+ # is the only one that can process signals.
168+ thread .start_new_thread (send_signals , ())
169+ timed_acquire ()
170+ # Wait for thread to finish
171+ done .acquire ()
172+ # This allows for some timing and scheduling imprecision
173+ self .assertLess (self .end - self .start , 2.0 )
174+ self .assertGreater (self .end - self .start , 0.3 )
175+ self .assertEqual (40 , self .sigs_recvd )
176+ finally :
177+ signal .signal (signal .SIGUSR1 , old_handler )
178+
69179
70180def test_main ():
71181 global signal_blackboard
0 commit comments