blob: 1ad6c63fea2e35453d905f8dd6ae714f2107b769 [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 Stinnerff40ecd2017-09-14 13:07:24 -07007from test import support
Antoine Pitrou88c60c92017-09-18 23:50:44 +02008import _thread as thread
Antoine Pitrou810023d2010-12-15 22:59:16 +00009import time
Michael W. Hudson43220ea2004-08-03 14:37:14 +000010
Christian Heimesde0b9622012-11-19 00:59:39 +010011if (sys.platform[:3] == 'win'):
Benjamin Petersone549ead2009-03-28 21:42:05 +000012 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
Victor Stinnerd5c355c2011-04-30 14:53:09 +020017USING_PTHREAD_COND = (sys.thread_info.name == 'pthread'
18 and sys.thread_info.lock == 'mutex+cond')
Michael W. Hudson43220ea2004-08-03 14:37:14 +000019
Guido van Rossum1bc535d2007-05-15 18:46:22 +000020def registerSignals(for_usr1, for_usr2, for_alrm):
Michael W. Hudson43220ea2004-08-03 14:37:14 +000021 usr1 = signal.signal(signal.SIGUSR1, for_usr1)
22 usr2 = signal.signal(signal.SIGUSR2, for_usr2)
23 alrm = signal.signal(signal.SIGALRM, for_alrm)
24 return usr1, usr2, alrm
25
26
Fred Drakedb390c12005-10-28 14:39:47 +000027# The signal handler. Just note that the signal occurred and
Michael W. Hudson43220ea2004-08-03 14:37:14 +000028# from who.
29def handle_signals(sig,frame):
Tim Peters6db15d72004-08-04 02:36:18 +000030 signal_blackboard[sig]['tripped'] += 1
Michael W. Hudson43220ea2004-08-03 14:37:14 +000031 signal_blackboard[sig]['tripped_by'] = thread.get_ident()
32
33# a function that will be spawned as a separate thread.
34def send_signals():
35 os.kill(process_pid, signal.SIGUSR1)
36 os.kill(process_pid, signal.SIGUSR2)
37 signalled_all.release()
38
39class ThreadSignals(unittest.TestCase):
Antoine Pitrou810023d2010-12-15 22:59:16 +000040
Michael W. Hudson43220ea2004-08-03 14:37:14 +000041 def test_signals(self):
Victor Stinnerff40ecd2017-09-14 13:07:24 -070042 with support.wait_threads_exit():
43 # Test signal handling semantics of threads.
44 # We spawn a thread, have the thread send two signals, and
45 # wait for it to finish. Check that we got both signals
46 # and that they were run by the main thread.
47 signalled_all.acquire()
48 self.spawnSignallingThread()
49 signalled_all.acquire()
50
Michael W. Hudson43220ea2004-08-03 14:37:14 +000051 # the signals that we asked the kernel to send
52 # will come back, but we don't know when.
53 # (it might even be after the thread exits
54 # and might be out of order.) If we haven't seen
55 # the signals yet, send yet another signal and
56 # wait for it return.
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000057 if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \
Michael W. Hudson43220ea2004-08-03 14:37:14 +000058 or signal_blackboard[signal.SIGUSR2]['tripped'] == 0:
Victor Stinner9abee722017-09-19 09:36:54 -070059 try:
60 signal.alarm(1)
61 signal.pause()
62 finally:
63 signal.alarm(0)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000064
65 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000066 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000067 thread.get_ident())
68 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000069 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000070 thread.get_ident())
Michael W. Hudson574a2512004-08-04 14:22:56 +000071 signalled_all.release()
Michael W. Hudson43220ea2004-08-03 14:37:14 +000072
73 def spawnSignallingThread(self):
74 thread.start_new_thread(send_signals, ())
Tim Peters6db15d72004-08-04 02:36:18 +000075
Antoine Pitrou810023d2010-12-15 22:59:16 +000076 def alarm_interrupt(self, sig, frame):
77 raise KeyboardInterrupt
78
Victor Stinner754851f2011-04-19 23:58:51 +020079 @unittest.skipIf(USING_PTHREAD_COND,
80 'POSIX condition variables cannot be interrupted')
Victor Stinner7d02d502014-02-18 09:19:48 +010081 # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD
82 @unittest.skipIf(sys.platform.startswith('openbsd'),
83 'lock cannot be interrupted on OpenBSD')
Antoine Pitrou810023d2010-12-15 22:59:16 +000084 def test_lock_acquire_interruption(self):
85 # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
86 # in a deadlock.
Antoine Pitroud3cccd22011-03-13 19:14:21 +010087 # XXX this test can fail when the legacy (non-semaphore) implementation
88 # of locks is used in thread_pthread.h, see issue #11223.
Antoine Pitrou810023d2010-12-15 22:59:16 +000089 oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
90 try:
91 lock = thread.allocate_lock()
92 lock.acquire()
93 signal.alarm(1)
Antoine Pitroud3cccd22011-03-13 19:14:21 +010094 t1 = time.time()
95 self.assertRaises(KeyboardInterrupt, lock.acquire, timeout=5)
96 dt = time.time() - t1
97 # Checking that KeyboardInterrupt was raised is not sufficient.
98 # We want to assert that lock.acquire() was interrupted because
99 # of the signal, not that the signal handler was called immediately
100 # after timeout return of lock.acquire() (which can fool assertRaises).
101 self.assertLess(dt, 3.0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000102 finally:
Victor Stinner9abee722017-09-19 09:36:54 -0700103 signal.alarm(0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000104 signal.signal(signal.SIGALRM, oldalrm)
105
Victor Stinner754851f2011-04-19 23:58:51 +0200106 @unittest.skipIf(USING_PTHREAD_COND,
107 'POSIX condition variables cannot be interrupted')
Victor Stinner7d02d502014-02-18 09:19:48 +0100108 # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD
109 @unittest.skipIf(sys.platform.startswith('openbsd'),
110 'lock cannot be interrupted on OpenBSD')
Antoine Pitrou810023d2010-12-15 22:59:16 +0000111 def test_rlock_acquire_interruption(self):
112 # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
113 # in a deadlock.
Antoine Pitroud3cccd22011-03-13 19:14:21 +0100114 # XXX this test can fail when the legacy (non-semaphore) implementation
115 # of locks is used in thread_pthread.h, see issue #11223.
Antoine Pitrou810023d2010-12-15 22:59:16 +0000116 oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
117 try:
118 rlock = thread.RLock()
119 # For reentrant locks, the initial acquisition must be in another
120 # thread.
121 def other_thread():
122 rlock.acquire()
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700123
124 with support.wait_threads_exit():
125 thread.start_new_thread(other_thread, ())
126 # Wait until we can't acquire it without blocking...
127 while rlock.acquire(blocking=False):
128 rlock.release()
129 time.sleep(0.01)
130 signal.alarm(1)
131 t1 = time.time()
132 self.assertRaises(KeyboardInterrupt, rlock.acquire, timeout=5)
133 dt = time.time() - t1
134 # See rationale above in test_lock_acquire_interruption
135 self.assertLess(dt, 3.0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000136 finally:
Victor Stinner9abee722017-09-19 09:36:54 -0700137 signal.alarm(0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000138 signal.signal(signal.SIGALRM, oldalrm)
139
140 def acquire_retries_on_intr(self, lock):
141 self.sig_recvd = False
142 def my_handler(signal, frame):
143 self.sig_recvd = True
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700144
Antoine Pitrou810023d2010-12-15 22:59:16 +0000145 old_handler = signal.signal(signal.SIGUSR1, my_handler)
146 try:
147 def other_thread():
148 # Acquire the lock in a non-main thread, so this test works for
149 # RLocks.
150 lock.acquire()
151 # Wait until the main thread is blocked in the lock acquire, and
152 # then wake it up with this.
153 time.sleep(0.5)
154 os.kill(process_pid, signal.SIGUSR1)
155 # Let the main thread take the interrupt, handle it, and retry
156 # the lock acquisition. Then we'll let it run.
157 time.sleep(0.5)
158 lock.release()
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700159
160 with support.wait_threads_exit():
161 thread.start_new_thread(other_thread, ())
162 # Wait until we can't acquire it without blocking...
163 while lock.acquire(blocking=False):
164 lock.release()
165 time.sleep(0.01)
166 result = lock.acquire() # Block while we receive a signal.
167 self.assertTrue(self.sig_recvd)
168 self.assertTrue(result)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000169 finally:
170 signal.signal(signal.SIGUSR1, old_handler)
171
172 def test_lock_acquire_retries_on_intr(self):
173 self.acquire_retries_on_intr(thread.allocate_lock())
174
175 def test_rlock_acquire_retries_on_intr(self):
176 self.acquire_retries_on_intr(thread.RLock())
177
178 def test_interrupted_timed_acquire(self):
179 # Test to make sure we recompute lock acquisition timeouts when we
180 # receive a signal. Check this by repeatedly interrupting a lock
181 # acquire in the main thread, and make sure that the lock acquire times
182 # out after the right amount of time.
Antoine Pitrou4fef5552010-12-15 23:38:50 +0000183 # NOTE: this test only behaves as expected if C signals get delivered
184 # to the main thread. Otherwise lock.acquire() itself doesn't get
185 # interrupted and the test trivially succeeds.
Antoine Pitrou810023d2010-12-15 22:59:16 +0000186 self.start = None
187 self.end = None
188 self.sigs_recvd = 0
189 done = thread.allocate_lock()
190 done.acquire()
191 lock = thread.allocate_lock()
192 lock.acquire()
193 def my_handler(signum, frame):
194 self.sigs_recvd += 1
195 old_handler = signal.signal(signal.SIGUSR1, my_handler)
196 try:
197 def timed_acquire():
198 self.start = time.time()
199 lock.acquire(timeout=0.5)
200 self.end = time.time()
201 def send_signals():
202 for _ in range(40):
Antoine Pitrou4fef5552010-12-15 23:38:50 +0000203 time.sleep(0.02)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000204 os.kill(process_pid, signal.SIGUSR1)
205 done.release()
206
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700207 with support.wait_threads_exit():
208 # Send the signals from the non-main thread, since the main thread
209 # is the only one that can process signals.
210 thread.start_new_thread(send_signals, ())
211 timed_acquire()
212 # Wait for thread to finish
213 done.acquire()
214 # This allows for some timing and scheduling imprecision
215 self.assertLess(self.end - self.start, 2.0)
216 self.assertGreater(self.end - self.start, 0.3)
217 # If the signal is received several times before PyErr_CheckSignals()
218 # is called, the handler will get called less than 40 times. Just
219 # check it's been called at least once.
220 self.assertGreater(self.sigs_recvd, 0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000221 finally:
222 signal.signal(signal.SIGUSR1, old_handler)
223
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000224
225def test_main():
Michael W. Hudson574a2512004-08-04 14:22:56 +0000226 global signal_blackboard
Tim Petersd1b78272004-08-07 06:03:09 +0000227
Michael W. Hudson574a2512004-08-04 14:22:56 +0000228 signal_blackboard = { signal.SIGUSR1 : {'tripped': 0, 'tripped_by': 0 },
229 signal.SIGUSR2 : {'tripped': 0, 'tripped_by': 0 },
230 signal.SIGALRM : {'tripped': 0, 'tripped_by': 0 } }
231
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000232 oldsigs = registerSignals(handle_signals, handle_signals, handle_signals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000233 try:
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700234 support.run_unittest(ThreadSignals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000235 finally:
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000236 registerSignals(*oldsigs)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000237
238if __name__ == '__main__':
239 test_main()