blob: fa26583472b1b429f4f8df4bf89a483d0d6a5f70 [file] [log] [blame]
Michael W. Hudson43220ea2004-08-03 14:37:14 +00001"""PyUnit testing that threads honor our signal semantics"""
2
3import unittest
Michael W. Hudson43220ea2004-08-03 14:37:14 +00004import signal
5import os
Fred Drake48187482004-08-03 16:14:13 +00006import sys
Victor Stinner45df8202010-04-28 22:31:17 +00007from test.support import run_unittest, import_module
8thread = import_module('_thread')
Antoine Pitrou810023d2010-12-15 22:59:16 +00009import time
Michael W. Hudson43220ea2004-08-03 14:37:14 +000010
Benjamin Petersone549ead2009-03-28 21:42:05 +000011if sys.platform[:3] in ('win', 'os2') or sys.platform=='riscos':
12 raise unittest.SkipTest("Can't test signal on %s" % sys.platform)
Michael W. Hudson34fba3b2004-08-03 15:35:29 +000013
Michael W. Hudson43220ea2004-08-03 14:37:14 +000014process_pid = os.getpid()
15signalled_all=thread.allocate_lock()
16
17
Guido van Rossum1bc535d2007-05-15 18:46:22 +000018def registerSignals(for_usr1, for_usr2, for_alrm):
Michael W. Hudson43220ea2004-08-03 14:37:14 +000019 usr1 = signal.signal(signal.SIGUSR1, for_usr1)
20 usr2 = signal.signal(signal.SIGUSR2, for_usr2)
21 alrm = signal.signal(signal.SIGALRM, for_alrm)
22 return usr1, usr2, alrm
23
24
Fred Drakedb390c12005-10-28 14:39:47 +000025# The signal handler. Just note that the signal occurred and
Michael W. Hudson43220ea2004-08-03 14:37:14 +000026# from who.
27def handle_signals(sig,frame):
Tim Peters6db15d72004-08-04 02:36:18 +000028 signal_blackboard[sig]['tripped'] += 1
Michael W. Hudson43220ea2004-08-03 14:37:14 +000029 signal_blackboard[sig]['tripped_by'] = thread.get_ident()
30
31# a function that will be spawned as a separate thread.
32def send_signals():
Victor Stinner271b27e2011-04-05 02:29:30 +020033 print("send_signals: enter (thread %s)" % thread.get_ident(), file=sys.stderr)
34 print("send_signals: raise SIGUSR1", file=sys.stderr)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000035 os.kill(process_pid, signal.SIGUSR1)
Victor Stinner271b27e2011-04-05 02:29:30 +020036 print("send_signals: raise SIGUSR2", file=sys.stderr)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000037 os.kill(process_pid, signal.SIGUSR2)
Victor Stinner271b27e2011-04-05 02:29:30 +020038 print("send_signals: release signalled_all", file=sys.stderr)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000039 signalled_all.release()
Victor Stinner271b27e2011-04-05 02:29:30 +020040 print("send_signals: exit (thread %s)" % thread.get_ident(), file=sys.stderr)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000041
42class ThreadSignals(unittest.TestCase):
Antoine Pitrou810023d2010-12-15 22:59:16 +000043
Michael W. Hudson43220ea2004-08-03 14:37:14 +000044 def test_signals(self):
Antoine Pitrou810023d2010-12-15 22:59:16 +000045 # Test signal handling semantics of threads.
46 # We spawn a thread, have the thread send two signals, and
47 # wait for it to finish. Check that we got both signals
48 # and that they were run by the main thread.
Victor Stinner271b27e2011-04-05 02:29:30 +020049 print("test_signals: acquire lock (thread %s)" % thread.get_ident(), file=sys.stderr)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000050 signalled_all.acquire()
51 self.spawnSignallingThread()
Victor Stinner271b27e2011-04-05 02:29:30 +020052 print("test_signals: wait lock (thread %s)" % thread.get_ident(), file=sys.stderr)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000053 signalled_all.acquire()
Victor Stinner271b27e2011-04-05 02:29:30 +020054 print("test_signals: lock acquired", file=sys.stderr)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000055 # the signals that we asked the kernel to send
56 # will come back, but we don't know when.
57 # (it might even be after the thread exits
58 # and might be out of order.) If we haven't seen
59 # the signals yet, send yet another signal and
60 # wait for it return.
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000061 if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \
Michael W. Hudson43220ea2004-08-03 14:37:14 +000062 or signal_blackboard[signal.SIGUSR2]['tripped'] == 0:
Tim Peters6db15d72004-08-04 02:36:18 +000063 signal.alarm(1)
64 signal.pause()
65 signal.alarm(0)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000066
67 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000068 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000069 thread.get_ident())
70 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000071 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000072 thread.get_ident())
Michael W. Hudson574a2512004-08-04 14:22:56 +000073 signalled_all.release()
Michael W. Hudson43220ea2004-08-03 14:37:14 +000074
75 def spawnSignallingThread(self):
76 thread.start_new_thread(send_signals, ())
Tim Peters6db15d72004-08-04 02:36:18 +000077
Antoine Pitrou810023d2010-12-15 22:59:16 +000078 def alarm_interrupt(self, sig, frame):
79 raise KeyboardInterrupt
80
81 def test_lock_acquire_interruption(self):
82 # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
83 # in a deadlock.
Antoine Pitroud3cccd22011-03-13 19:14:21 +010084 # XXX this test can fail when the legacy (non-semaphore) implementation
85 # of locks is used in thread_pthread.h, see issue #11223.
Antoine Pitrou810023d2010-12-15 22:59:16 +000086 oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
87 try:
88 lock = thread.allocate_lock()
89 lock.acquire()
90 signal.alarm(1)
Antoine Pitroud3cccd22011-03-13 19:14:21 +010091 t1 = time.time()
92 self.assertRaises(KeyboardInterrupt, lock.acquire, timeout=5)
93 dt = time.time() - t1
94 # Checking that KeyboardInterrupt was raised is not sufficient.
95 # We want to assert that lock.acquire() was interrupted because
96 # of the signal, not that the signal handler was called immediately
97 # after timeout return of lock.acquire() (which can fool assertRaises).
98 self.assertLess(dt, 3.0)
Antoine Pitrou810023d2010-12-15 22:59:16 +000099 finally:
100 signal.signal(signal.SIGALRM, oldalrm)
101
102 def test_rlock_acquire_interruption(self):
103 # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
104 # in a deadlock.
Antoine Pitroud3cccd22011-03-13 19:14:21 +0100105 # XXX this test can fail when the legacy (non-semaphore) implementation
106 # of locks is used in thread_pthread.h, see issue #11223.
Antoine Pitrou810023d2010-12-15 22:59:16 +0000107 oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
108 try:
109 rlock = thread.RLock()
110 # For reentrant locks, the initial acquisition must be in another
111 # thread.
112 def other_thread():
113 rlock.acquire()
114 thread.start_new_thread(other_thread, ())
115 # Wait until we can't acquire it without blocking...
116 while rlock.acquire(blocking=False):
117 rlock.release()
118 time.sleep(0.01)
119 signal.alarm(1)
Antoine Pitroud3cccd22011-03-13 19:14:21 +0100120 t1 = time.time()
121 self.assertRaises(KeyboardInterrupt, rlock.acquire, timeout=5)
122 dt = time.time() - t1
123 # See rationale above in test_lock_acquire_interruption
124 self.assertLess(dt, 3.0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000125 finally:
126 signal.signal(signal.SIGALRM, oldalrm)
127
128 def acquire_retries_on_intr(self, lock):
129 self.sig_recvd = False
130 def my_handler(signal, frame):
131 self.sig_recvd = True
132 old_handler = signal.signal(signal.SIGUSR1, my_handler)
133 try:
134 def other_thread():
135 # Acquire the lock in a non-main thread, so this test works for
136 # RLocks.
137 lock.acquire()
138 # Wait until the main thread is blocked in the lock acquire, and
139 # then wake it up with this.
140 time.sleep(0.5)
141 os.kill(process_pid, signal.SIGUSR1)
142 # Let the main thread take the interrupt, handle it, and retry
143 # the lock acquisition. Then we'll let it run.
144 time.sleep(0.5)
145 lock.release()
146 thread.start_new_thread(other_thread, ())
147 # Wait until we can't acquire it without blocking...
148 while lock.acquire(blocking=False):
149 lock.release()
150 time.sleep(0.01)
151 result = lock.acquire() # Block while we receive a signal.
152 self.assertTrue(self.sig_recvd)
153 self.assertTrue(result)
154 finally:
155 signal.signal(signal.SIGUSR1, old_handler)
156
157 def test_lock_acquire_retries_on_intr(self):
158 self.acquire_retries_on_intr(thread.allocate_lock())
159
160 def test_rlock_acquire_retries_on_intr(self):
161 self.acquire_retries_on_intr(thread.RLock())
162
163 def test_interrupted_timed_acquire(self):
164 # Test to make sure we recompute lock acquisition timeouts when we
165 # receive a signal. Check this by repeatedly interrupting a lock
166 # acquire in the main thread, and make sure that the lock acquire times
167 # out after the right amount of time.
Antoine Pitrou4fef5552010-12-15 23:38:50 +0000168 # NOTE: this test only behaves as expected if C signals get delivered
169 # to the main thread. Otherwise lock.acquire() itself doesn't get
170 # interrupted and the test trivially succeeds.
Antoine Pitrou810023d2010-12-15 22:59:16 +0000171 self.start = None
172 self.end = None
173 self.sigs_recvd = 0
174 done = thread.allocate_lock()
175 done.acquire()
176 lock = thread.allocate_lock()
177 lock.acquire()
178 def my_handler(signum, frame):
179 self.sigs_recvd += 1
180 old_handler = signal.signal(signal.SIGUSR1, my_handler)
181 try:
182 def timed_acquire():
183 self.start = time.time()
184 lock.acquire(timeout=0.5)
185 self.end = time.time()
186 def send_signals():
187 for _ in range(40):
Antoine Pitrou4fef5552010-12-15 23:38:50 +0000188 time.sleep(0.02)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000189 os.kill(process_pid, signal.SIGUSR1)
190 done.release()
191
192 # Send the signals from the non-main thread, since the main thread
193 # is the only one that can process signals.
194 thread.start_new_thread(send_signals, ())
195 timed_acquire()
196 # Wait for thread to finish
197 done.acquire()
198 # This allows for some timing and scheduling imprecision
199 self.assertLess(self.end - self.start, 2.0)
200 self.assertGreater(self.end - self.start, 0.3)
Antoine Pitrou4fef5552010-12-15 23:38:50 +0000201 # If the signal is received several times before PyErr_CheckSignals()
Antoine Pitroud8f37ad2011-01-02 16:16:09 +0000202 # is called, the handler will get called less than 40 times. Just
203 # check it's been called at least once.
204 self.assertGreater(self.sigs_recvd, 0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000205 finally:
206 signal.signal(signal.SIGUSR1, old_handler)
207
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000208
209def test_main():
Michael W. Hudson574a2512004-08-04 14:22:56 +0000210 global signal_blackboard
Tim Petersd1b78272004-08-07 06:03:09 +0000211
Michael W. Hudson574a2512004-08-04 14:22:56 +0000212 signal_blackboard = { signal.SIGUSR1 : {'tripped': 0, 'tripped_by': 0 },
213 signal.SIGUSR2 : {'tripped': 0, 'tripped_by': 0 },
214 signal.SIGALRM : {'tripped': 0, 'tripped_by': 0 } }
215
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000216 oldsigs = registerSignals(handle_signals, handle_signals, handle_signals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000217 try:
Michael W. Hudson574a2512004-08-04 14:22:56 +0000218 run_unittest(ThreadSignals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000219 finally:
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000220 registerSignals(*oldsigs)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000221
222if __name__ == '__main__':
223 test_main()