blob: 15e8078e936623bb80210f980cc54a0f37d449d5 [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
Hai Shie80697d2020-05-28 06:10:27 +08008from test.support import threading_helper
Antoine Pitrou88c60c92017-09-18 23:50:44 +02009import _thread as thread
Antoine Pitrou810023d2010-12-15 22:59:16 +000010import time
Michael W. Hudson43220ea2004-08-03 14:37:14 +000011
Christian Heimesde0b9622012-11-19 00:59:39 +010012if (sys.platform[:3] == 'win'):
Benjamin Petersone549ead2009-03-28 21:42:05 +000013 raise unittest.SkipTest("Can't test signal on %s" % sys.platform)
Michael W. Hudson34fba3b2004-08-03 15:35:29 +000014
Michael W. Hudson43220ea2004-08-03 14:37:14 +000015process_pid = os.getpid()
16signalled_all=thread.allocate_lock()
17
Victor Stinnerd5c355c2011-04-30 14:53:09 +020018USING_PTHREAD_COND = (sys.thread_info.name == 'pthread'
19 and sys.thread_info.lock == 'mutex+cond')
Michael W. Hudson43220ea2004-08-03 14:37:14 +000020
Guido van Rossum1bc535d2007-05-15 18:46:22 +000021def registerSignals(for_usr1, for_usr2, for_alrm):
Michael W. Hudson43220ea2004-08-03 14:37:14 +000022 usr1 = signal.signal(signal.SIGUSR1, for_usr1)
23 usr2 = signal.signal(signal.SIGUSR2, for_usr2)
24 alrm = signal.signal(signal.SIGALRM, for_alrm)
25 return usr1, usr2, alrm
26
27
Fred Drakedb390c12005-10-28 14:39:47 +000028# The signal handler. Just note that the signal occurred and
Michael W. Hudson43220ea2004-08-03 14:37:14 +000029# from who.
30def handle_signals(sig,frame):
Tim Peters6db15d72004-08-04 02:36:18 +000031 signal_blackboard[sig]['tripped'] += 1
Michael W. Hudson43220ea2004-08-03 14:37:14 +000032 signal_blackboard[sig]['tripped_by'] = thread.get_ident()
33
34# a function that will be spawned as a separate thread.
35def send_signals():
36 os.kill(process_pid, signal.SIGUSR1)
37 os.kill(process_pid, signal.SIGUSR2)
38 signalled_all.release()
39
40class ThreadSignals(unittest.TestCase):
Antoine Pitrou810023d2010-12-15 22:59:16 +000041
Michael W. Hudson43220ea2004-08-03 14:37:14 +000042 def test_signals(self):
Hai Shie80697d2020-05-28 06:10:27 +080043 with threading_helper.wait_threads_exit():
Victor Stinnerff40ecd2017-09-14 13:07:24 -070044 # Test signal handling semantics of threads.
45 # We spawn a thread, have the thread send two signals, and
46 # wait for it to finish. Check that we got both signals
47 # and that they were run by the main thread.
48 signalled_all.acquire()
49 self.spawnSignallingThread()
50 signalled_all.acquire()
51
Michael W. Hudson43220ea2004-08-03 14:37:14 +000052 # the signals that we asked the kernel to send
53 # will come back, but we don't know when.
54 # (it might even be after the thread exits
55 # and might be out of order.) If we haven't seen
56 # the signals yet, send yet another signal and
57 # wait for it return.
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000058 if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \
Michael W. Hudson43220ea2004-08-03 14:37:14 +000059 or signal_blackboard[signal.SIGUSR2]['tripped'] == 0:
Victor Stinner9abee722017-09-19 09:36:54 -070060 try:
61 signal.alarm(1)
62 signal.pause()
63 finally:
64 signal.alarm(0)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000065
66 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000067 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000068 thread.get_ident())
69 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000070 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000071 thread.get_ident())
Michael W. Hudson574a2512004-08-04 14:22:56 +000072 signalled_all.release()
Michael W. Hudson43220ea2004-08-03 14:37:14 +000073
74 def spawnSignallingThread(self):
75 thread.start_new_thread(send_signals, ())
Tim Peters6db15d72004-08-04 02:36:18 +000076
Antoine Pitrou810023d2010-12-15 22:59:16 +000077 def alarm_interrupt(self, sig, frame):
78 raise KeyboardInterrupt
79
Victor Stinner754851f2011-04-19 23:58:51 +020080 @unittest.skipIf(USING_PTHREAD_COND,
81 'POSIX condition variables cannot be interrupted')
Benjamin Peterson5b10d512018-09-12 13:48:03 -070082 @unittest.skipIf(sys.platform.startswith('linux') and
83 not sys.thread_info.version,
84 'Issue 34004: musl does not allow interruption of locks '
85 'by signals.')
Victor Stinner7d02d502014-02-18 09:19:48 +010086 # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD
87 @unittest.skipIf(sys.platform.startswith('openbsd'),
88 'lock cannot be interrupted on OpenBSD')
Antoine Pitrou810023d2010-12-15 22:59:16 +000089 def test_lock_acquire_interruption(self):
90 # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
91 # in a deadlock.
Antoine Pitroud3cccd22011-03-13 19:14:21 +010092 # XXX this test can fail when the legacy (non-semaphore) implementation
93 # of locks is used in thread_pthread.h, see issue #11223.
Antoine Pitrou810023d2010-12-15 22:59:16 +000094 oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
95 try:
96 lock = thread.allocate_lock()
97 lock.acquire()
98 signal.alarm(1)
Victor Stinner2cf4c202018-12-17 09:36:36 +010099 t1 = time.monotonic()
Antoine Pitroud3cccd22011-03-13 19:14:21 +0100100 self.assertRaises(KeyboardInterrupt, lock.acquire, timeout=5)
Victor Stinner2cf4c202018-12-17 09:36:36 +0100101 dt = time.monotonic() - t1
Antoine Pitroud3cccd22011-03-13 19:14:21 +0100102 # Checking that KeyboardInterrupt was raised is not sufficient.
103 # We want to assert that lock.acquire() was interrupted because
104 # of the signal, not that the signal handler was called immediately
105 # after timeout return of lock.acquire() (which can fool assertRaises).
106 self.assertLess(dt, 3.0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000107 finally:
Victor Stinner9abee722017-09-19 09:36:54 -0700108 signal.alarm(0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000109 signal.signal(signal.SIGALRM, oldalrm)
110
Victor Stinner754851f2011-04-19 23:58:51 +0200111 @unittest.skipIf(USING_PTHREAD_COND,
112 'POSIX condition variables cannot be interrupted')
Benjamin Peterson5b10d512018-09-12 13:48:03 -0700113 @unittest.skipIf(sys.platform.startswith('linux') and
114 not sys.thread_info.version,
115 'Issue 34004: musl does not allow interruption of locks '
116 'by signals.')
Victor Stinner7d02d502014-02-18 09:19:48 +0100117 # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD
118 @unittest.skipIf(sys.platform.startswith('openbsd'),
119 'lock cannot be interrupted on OpenBSD')
Antoine Pitrou810023d2010-12-15 22:59:16 +0000120 def test_rlock_acquire_interruption(self):
121 # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
122 # in a deadlock.
Antoine Pitroud3cccd22011-03-13 19:14:21 +0100123 # XXX this test can fail when the legacy (non-semaphore) implementation
124 # of locks is used in thread_pthread.h, see issue #11223.
Antoine Pitrou810023d2010-12-15 22:59:16 +0000125 oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
126 try:
127 rlock = thread.RLock()
128 # For reentrant locks, the initial acquisition must be in another
129 # thread.
130 def other_thread():
131 rlock.acquire()
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700132
Hai Shie80697d2020-05-28 06:10:27 +0800133 with threading_helper.wait_threads_exit():
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700134 thread.start_new_thread(other_thread, ())
135 # Wait until we can't acquire it without blocking...
136 while rlock.acquire(blocking=False):
137 rlock.release()
138 time.sleep(0.01)
139 signal.alarm(1)
Victor Stinner2cf4c202018-12-17 09:36:36 +0100140 t1 = time.monotonic()
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700141 self.assertRaises(KeyboardInterrupt, rlock.acquire, timeout=5)
Victor Stinner2cf4c202018-12-17 09:36:36 +0100142 dt = time.monotonic() - t1
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700143 # See rationale above in test_lock_acquire_interruption
144 self.assertLess(dt, 3.0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000145 finally:
Victor Stinner9abee722017-09-19 09:36:54 -0700146 signal.alarm(0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000147 signal.signal(signal.SIGALRM, oldalrm)
148
149 def acquire_retries_on_intr(self, lock):
150 self.sig_recvd = False
151 def my_handler(signal, frame):
152 self.sig_recvd = True
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700153
Antoine Pitrou810023d2010-12-15 22:59:16 +0000154 old_handler = signal.signal(signal.SIGUSR1, my_handler)
155 try:
156 def other_thread():
157 # Acquire the lock in a non-main thread, so this test works for
158 # RLocks.
159 lock.acquire()
160 # Wait until the main thread is blocked in the lock acquire, and
161 # then wake it up with this.
162 time.sleep(0.5)
163 os.kill(process_pid, signal.SIGUSR1)
164 # Let the main thread take the interrupt, handle it, and retry
165 # the lock acquisition. Then we'll let it run.
166 time.sleep(0.5)
167 lock.release()
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700168
Hai Shie80697d2020-05-28 06:10:27 +0800169 with threading_helper.wait_threads_exit():
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700170 thread.start_new_thread(other_thread, ())
171 # Wait until we can't acquire it without blocking...
172 while lock.acquire(blocking=False):
173 lock.release()
174 time.sleep(0.01)
175 result = lock.acquire() # Block while we receive a signal.
176 self.assertTrue(self.sig_recvd)
177 self.assertTrue(result)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000178 finally:
179 signal.signal(signal.SIGUSR1, old_handler)
180
181 def test_lock_acquire_retries_on_intr(self):
182 self.acquire_retries_on_intr(thread.allocate_lock())
183
184 def test_rlock_acquire_retries_on_intr(self):
185 self.acquire_retries_on_intr(thread.RLock())
186
187 def test_interrupted_timed_acquire(self):
188 # Test to make sure we recompute lock acquisition timeouts when we
189 # receive a signal. Check this by repeatedly interrupting a lock
190 # acquire in the main thread, and make sure that the lock acquire times
191 # out after the right amount of time.
Antoine Pitrou4fef5552010-12-15 23:38:50 +0000192 # NOTE: this test only behaves as expected if C signals get delivered
193 # to the main thread. Otherwise lock.acquire() itself doesn't get
194 # interrupted and the test trivially succeeds.
Antoine Pitrou810023d2010-12-15 22:59:16 +0000195 self.start = None
196 self.end = None
197 self.sigs_recvd = 0
198 done = thread.allocate_lock()
199 done.acquire()
200 lock = thread.allocate_lock()
201 lock.acquire()
202 def my_handler(signum, frame):
203 self.sigs_recvd += 1
204 old_handler = signal.signal(signal.SIGUSR1, my_handler)
205 try:
206 def timed_acquire():
Victor Stinner2cf4c202018-12-17 09:36:36 +0100207 self.start = time.monotonic()
Antoine Pitrou810023d2010-12-15 22:59:16 +0000208 lock.acquire(timeout=0.5)
Victor Stinner2cf4c202018-12-17 09:36:36 +0100209 self.end = time.monotonic()
Antoine Pitrou810023d2010-12-15 22:59:16 +0000210 def send_signals():
211 for _ in range(40):
Antoine Pitrou4fef5552010-12-15 23:38:50 +0000212 time.sleep(0.02)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000213 os.kill(process_pid, signal.SIGUSR1)
214 done.release()
215
Hai Shie80697d2020-05-28 06:10:27 +0800216 with threading_helper.wait_threads_exit():
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700217 # Send the signals from the non-main thread, since the main thread
218 # is the only one that can process signals.
219 thread.start_new_thread(send_signals, ())
220 timed_acquire()
221 # Wait for thread to finish
222 done.acquire()
223 # This allows for some timing and scheduling imprecision
224 self.assertLess(self.end - self.start, 2.0)
225 self.assertGreater(self.end - self.start, 0.3)
226 # If the signal is received several times before PyErr_CheckSignals()
227 # is called, the handler will get called less than 40 times. Just
228 # check it's been called at least once.
229 self.assertGreater(self.sigs_recvd, 0)
Antoine Pitrou810023d2010-12-15 22:59:16 +0000230 finally:
231 signal.signal(signal.SIGUSR1, old_handler)
232
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000233
234def test_main():
Michael W. Hudson574a2512004-08-04 14:22:56 +0000235 global signal_blackboard
Tim Petersd1b78272004-08-07 06:03:09 +0000236
Michael W. Hudson574a2512004-08-04 14:22:56 +0000237 signal_blackboard = { signal.SIGUSR1 : {'tripped': 0, 'tripped_by': 0 },
238 signal.SIGUSR2 : {'tripped': 0, 'tripped_by': 0 },
239 signal.SIGALRM : {'tripped': 0, 'tripped_by': 0 } }
240
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000241 oldsigs = registerSignals(handle_signals, handle_signals, handle_signals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000242 try:
Victor Stinnerff40ecd2017-09-14 13:07:24 -0700243 support.run_unittest(ThreadSignals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000244 finally:
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000245 registerSignals(*oldsigs)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000246
247if __name__ == '__main__':
248 test_main()