| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 1 | """PyUnit testing that threads honor our signal semantics""" | 
 | 2 |  | 
 | 3 | import unittest | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 4 | import signal | 
 | 5 | import os | 
| Fred Drake | 4818748 | 2004-08-03 16:14:13 +0000 | [diff] [blame] | 6 | import sys | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 7 | from test import support | 
| Antoine Pitrou | 88c60c9 | 2017-09-18 23:50:44 +0200 | [diff] [blame] | 8 | import _thread as thread | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 9 | import time | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 10 |  | 
| Christian Heimes | de0b962 | 2012-11-19 00:59:39 +0100 | [diff] [blame] | 11 | if (sys.platform[:3] == 'win'): | 
| Benjamin Peterson | e549ead | 2009-03-28 21:42:05 +0000 | [diff] [blame] | 12 |     raise unittest.SkipTest("Can't test signal on %s" % sys.platform) | 
| Michael W. Hudson | 34fba3b | 2004-08-03 15:35:29 +0000 | [diff] [blame] | 13 |  | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 14 | process_pid = os.getpid() | 
 | 15 | signalled_all=thread.allocate_lock() | 
 | 16 |  | 
| Victor Stinner | d5c355c | 2011-04-30 14:53:09 +0200 | [diff] [blame] | 17 | USING_PTHREAD_COND = (sys.thread_info.name == 'pthread' | 
 | 18 |                       and sys.thread_info.lock == 'mutex+cond') | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 19 |  | 
| Guido van Rossum | 1bc535d | 2007-05-15 18:46:22 +0000 | [diff] [blame] | 20 | def registerSignals(for_usr1, for_usr2, for_alrm): | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 21 |     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 Drake | db390c1 | 2005-10-28 14:39:47 +0000 | [diff] [blame] | 27 | # The signal handler. Just note that the signal occurred and | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 28 | # from who. | 
 | 29 | def handle_signals(sig,frame): | 
| Tim Peters | 6db15d7 | 2004-08-04 02:36:18 +0000 | [diff] [blame] | 30 |     signal_blackboard[sig]['tripped'] += 1 | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 31 |     signal_blackboard[sig]['tripped_by'] = thread.get_ident() | 
 | 32 |  | 
 | 33 | # a function that will be spawned as a separate thread. | 
 | 34 | def send_signals(): | 
 | 35 |     os.kill(process_pid, signal.SIGUSR1) | 
 | 36 |     os.kill(process_pid, signal.SIGUSR2) | 
 | 37 |     signalled_all.release() | 
 | 38 |  | 
 | 39 | class ThreadSignals(unittest.TestCase): | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 40 |  | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 41 |     def test_signals(self): | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 42 |         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. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 51 |         # 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 Wouters | 00ee7ba | 2006-08-21 19:07:27 +0000 | [diff] [blame] | 57 |         if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \ | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 58 |            or signal_blackboard[signal.SIGUSR2]['tripped'] == 0: | 
| Victor Stinner | 9abee72 | 2017-09-19 09:36:54 -0700 | [diff] [blame] | 59 |             try: | 
 | 60 |                 signal.alarm(1) | 
 | 61 |                 signal.pause() | 
 | 62 |             finally: | 
 | 63 |                 signal.alarm(0) | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 64 |  | 
 | 65 |         self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1) | 
| Tim Peters | 6db15d7 | 2004-08-04 02:36:18 +0000 | [diff] [blame] | 66 |         self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'], | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 67 |                            thread.get_ident()) | 
 | 68 |         self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped'], 1) | 
| Tim Peters | 6db15d7 | 2004-08-04 02:36:18 +0000 | [diff] [blame] | 69 |         self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped_by'], | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 70 |                            thread.get_ident()) | 
| Michael W. Hudson | 574a251 | 2004-08-04 14:22:56 +0000 | [diff] [blame] | 71 |         signalled_all.release() | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 72 |  | 
 | 73 |     def spawnSignallingThread(self): | 
 | 74 |         thread.start_new_thread(send_signals, ()) | 
| Tim Peters | 6db15d7 | 2004-08-04 02:36:18 +0000 | [diff] [blame] | 75 |  | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 76 |     def alarm_interrupt(self, sig, frame): | 
 | 77 |         raise KeyboardInterrupt | 
 | 78 |  | 
| Victor Stinner | 754851f | 2011-04-19 23:58:51 +0200 | [diff] [blame] | 79 |     @unittest.skipIf(USING_PTHREAD_COND, | 
 | 80 |                      'POSIX condition variables cannot be interrupted') | 
| Benjamin Peterson | 5b10d51 | 2018-09-12 13:48:03 -0700 | [diff] [blame] | 81 |     @unittest.skipIf(sys.platform.startswith('linux') and | 
 | 82 |                      not sys.thread_info.version, | 
 | 83 |                      'Issue 34004: musl does not allow interruption of locks ' | 
 | 84 |                      'by signals.') | 
| Victor Stinner | 7d02d50 | 2014-02-18 09:19:48 +0100 | [diff] [blame] | 85 |     # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD | 
 | 86 |     @unittest.skipIf(sys.platform.startswith('openbsd'), | 
 | 87 |                      'lock cannot be interrupted on OpenBSD') | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 88 |     def test_lock_acquire_interruption(self): | 
 | 89 |         # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck | 
 | 90 |         # in a deadlock. | 
| Antoine Pitrou | d3cccd2 | 2011-03-13 19:14:21 +0100 | [diff] [blame] | 91 |         # XXX this test can fail when the legacy (non-semaphore) implementation | 
 | 92 |         # of locks is used in thread_pthread.h, see issue #11223. | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 93 |         oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt) | 
 | 94 |         try: | 
 | 95 |             lock = thread.allocate_lock() | 
 | 96 |             lock.acquire() | 
 | 97 |             signal.alarm(1) | 
| Victor Stinner | 2cf4c20 | 2018-12-17 09:36:36 +0100 | [diff] [blame] | 98 |             t1 = time.monotonic() | 
| Antoine Pitrou | d3cccd2 | 2011-03-13 19:14:21 +0100 | [diff] [blame] | 99 |             self.assertRaises(KeyboardInterrupt, lock.acquire, timeout=5) | 
| Victor Stinner | 2cf4c20 | 2018-12-17 09:36:36 +0100 | [diff] [blame] | 100 |             dt = time.monotonic() - t1 | 
| Antoine Pitrou | d3cccd2 | 2011-03-13 19:14:21 +0100 | [diff] [blame] | 101 |             # Checking that KeyboardInterrupt was raised is not sufficient. | 
 | 102 |             # We want to assert that lock.acquire() was interrupted because | 
 | 103 |             # of the signal, not that the signal handler was called immediately | 
 | 104 |             # after timeout return of lock.acquire() (which can fool assertRaises). | 
 | 105 |             self.assertLess(dt, 3.0) | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 106 |         finally: | 
| Victor Stinner | 9abee72 | 2017-09-19 09:36:54 -0700 | [diff] [blame] | 107 |             signal.alarm(0) | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 108 |             signal.signal(signal.SIGALRM, oldalrm) | 
 | 109 |  | 
| Victor Stinner | 754851f | 2011-04-19 23:58:51 +0200 | [diff] [blame] | 110 |     @unittest.skipIf(USING_PTHREAD_COND, | 
 | 111 |                      'POSIX condition variables cannot be interrupted') | 
| Benjamin Peterson | 5b10d51 | 2018-09-12 13:48:03 -0700 | [diff] [blame] | 112 |     @unittest.skipIf(sys.platform.startswith('linux') and | 
 | 113 |                      not sys.thread_info.version, | 
 | 114 |                      'Issue 34004: musl does not allow interruption of locks ' | 
 | 115 |                      'by signals.') | 
| Victor Stinner | 7d02d50 | 2014-02-18 09:19:48 +0100 | [diff] [blame] | 116 |     # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD | 
 | 117 |     @unittest.skipIf(sys.platform.startswith('openbsd'), | 
 | 118 |                      'lock cannot be interrupted on OpenBSD') | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 119 |     def test_rlock_acquire_interruption(self): | 
 | 120 |         # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck | 
 | 121 |         # in a deadlock. | 
| Antoine Pitrou | d3cccd2 | 2011-03-13 19:14:21 +0100 | [diff] [blame] | 122 |         # XXX this test can fail when the legacy (non-semaphore) implementation | 
 | 123 |         # of locks is used in thread_pthread.h, see issue #11223. | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 124 |         oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt) | 
 | 125 |         try: | 
 | 126 |             rlock = thread.RLock() | 
 | 127 |             # For reentrant locks, the initial acquisition must be in another | 
 | 128 |             # thread. | 
 | 129 |             def other_thread(): | 
 | 130 |                 rlock.acquire() | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 131 |  | 
 | 132 |             with support.wait_threads_exit(): | 
 | 133 |                 thread.start_new_thread(other_thread, ()) | 
 | 134 |                 # Wait until we can't acquire it without blocking... | 
 | 135 |                 while rlock.acquire(blocking=False): | 
 | 136 |                     rlock.release() | 
 | 137 |                     time.sleep(0.01) | 
 | 138 |                 signal.alarm(1) | 
| Victor Stinner | 2cf4c20 | 2018-12-17 09:36:36 +0100 | [diff] [blame] | 139 |                 t1 = time.monotonic() | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 140 |                 self.assertRaises(KeyboardInterrupt, rlock.acquire, timeout=5) | 
| Victor Stinner | 2cf4c20 | 2018-12-17 09:36:36 +0100 | [diff] [blame] | 141 |                 dt = time.monotonic() - t1 | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 142 |                 # See rationale above in test_lock_acquire_interruption | 
 | 143 |                 self.assertLess(dt, 3.0) | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 144 |         finally: | 
| Victor Stinner | 9abee72 | 2017-09-19 09:36:54 -0700 | [diff] [blame] | 145 |             signal.alarm(0) | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 146 |             signal.signal(signal.SIGALRM, oldalrm) | 
 | 147 |  | 
 | 148 |     def acquire_retries_on_intr(self, lock): | 
 | 149 |         self.sig_recvd = False | 
 | 150 |         def my_handler(signal, frame): | 
 | 151 |             self.sig_recvd = True | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 152 |  | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 153 |         old_handler = signal.signal(signal.SIGUSR1, my_handler) | 
 | 154 |         try: | 
 | 155 |             def other_thread(): | 
 | 156 |                 # Acquire the lock in a non-main thread, so this test works for | 
 | 157 |                 # RLocks. | 
 | 158 |                 lock.acquire() | 
 | 159 |                 # Wait until the main thread is blocked in the lock acquire, and | 
 | 160 |                 # then wake it up with this. | 
 | 161 |                 time.sleep(0.5) | 
 | 162 |                 os.kill(process_pid, signal.SIGUSR1) | 
 | 163 |                 # Let the main thread take the interrupt, handle it, and retry | 
 | 164 |                 # the lock acquisition.  Then we'll let it run. | 
 | 165 |                 time.sleep(0.5) | 
 | 166 |                 lock.release() | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 167 |  | 
 | 168 |             with support.wait_threads_exit(): | 
 | 169 |                 thread.start_new_thread(other_thread, ()) | 
 | 170 |                 # Wait until we can't acquire it without blocking... | 
 | 171 |                 while lock.acquire(blocking=False): | 
 | 172 |                     lock.release() | 
 | 173 |                     time.sleep(0.01) | 
 | 174 |                 result = lock.acquire()  # Block while we receive a signal. | 
 | 175 |                 self.assertTrue(self.sig_recvd) | 
 | 176 |                 self.assertTrue(result) | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 177 |         finally: | 
 | 178 |             signal.signal(signal.SIGUSR1, old_handler) | 
 | 179 |  | 
 | 180 |     def test_lock_acquire_retries_on_intr(self): | 
 | 181 |         self.acquire_retries_on_intr(thread.allocate_lock()) | 
 | 182 |  | 
 | 183 |     def test_rlock_acquire_retries_on_intr(self): | 
 | 184 |         self.acquire_retries_on_intr(thread.RLock()) | 
 | 185 |  | 
 | 186 |     def test_interrupted_timed_acquire(self): | 
 | 187 |         # Test to make sure we recompute lock acquisition timeouts when we | 
 | 188 |         # receive a signal.  Check this by repeatedly interrupting a lock | 
 | 189 |         # acquire in the main thread, and make sure that the lock acquire times | 
 | 190 |         # out after the right amount of time. | 
| Antoine Pitrou | 4fef555 | 2010-12-15 23:38:50 +0000 | [diff] [blame] | 191 |         # NOTE: this test only behaves as expected if C signals get delivered | 
 | 192 |         # to the main thread.  Otherwise lock.acquire() itself doesn't get | 
 | 193 |         # interrupted and the test trivially succeeds. | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 194 |         self.start = None | 
 | 195 |         self.end = None | 
 | 196 |         self.sigs_recvd = 0 | 
 | 197 |         done = thread.allocate_lock() | 
 | 198 |         done.acquire() | 
 | 199 |         lock = thread.allocate_lock() | 
 | 200 |         lock.acquire() | 
 | 201 |         def my_handler(signum, frame): | 
 | 202 |             self.sigs_recvd += 1 | 
 | 203 |         old_handler = signal.signal(signal.SIGUSR1, my_handler) | 
 | 204 |         try: | 
 | 205 |             def timed_acquire(): | 
| Victor Stinner | 2cf4c20 | 2018-12-17 09:36:36 +0100 | [diff] [blame] | 206 |                 self.start = time.monotonic() | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 207 |                 lock.acquire(timeout=0.5) | 
| Victor Stinner | 2cf4c20 | 2018-12-17 09:36:36 +0100 | [diff] [blame] | 208 |                 self.end = time.monotonic() | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 209 |             def send_signals(): | 
 | 210 |                 for _ in range(40): | 
| Antoine Pitrou | 4fef555 | 2010-12-15 23:38:50 +0000 | [diff] [blame] | 211 |                     time.sleep(0.02) | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 212 |                     os.kill(process_pid, signal.SIGUSR1) | 
 | 213 |                 done.release() | 
 | 214 |  | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 215 |             with support.wait_threads_exit(): | 
 | 216 |                 # Send the signals from the non-main thread, since the main thread | 
 | 217 |                 # is the only one that can process signals. | 
 | 218 |                 thread.start_new_thread(send_signals, ()) | 
 | 219 |                 timed_acquire() | 
 | 220 |                 # Wait for thread to finish | 
 | 221 |                 done.acquire() | 
 | 222 |                 # This allows for some timing and scheduling imprecision | 
 | 223 |                 self.assertLess(self.end - self.start, 2.0) | 
 | 224 |                 self.assertGreater(self.end - self.start, 0.3) | 
 | 225 |                 # If the signal is received several times before PyErr_CheckSignals() | 
 | 226 |                 # is called, the handler will get called less than 40 times. Just | 
 | 227 |                 # check it's been called at least once. | 
 | 228 |                 self.assertGreater(self.sigs_recvd, 0) | 
| Antoine Pitrou | 810023d | 2010-12-15 22:59:16 +0000 | [diff] [blame] | 229 |         finally: | 
 | 230 |             signal.signal(signal.SIGUSR1, old_handler) | 
 | 231 |  | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 232 |  | 
 | 233 | def test_main(): | 
| Michael W. Hudson | 574a251 | 2004-08-04 14:22:56 +0000 | [diff] [blame] | 234 |     global signal_blackboard | 
| Tim Peters | d1b7827 | 2004-08-07 06:03:09 +0000 | [diff] [blame] | 235 |  | 
| Michael W. Hudson | 574a251 | 2004-08-04 14:22:56 +0000 | [diff] [blame] | 236 |     signal_blackboard = { signal.SIGUSR1 : {'tripped': 0, 'tripped_by': 0 }, | 
 | 237 |                           signal.SIGUSR2 : {'tripped': 0, 'tripped_by': 0 }, | 
 | 238 |                           signal.SIGALRM : {'tripped': 0, 'tripped_by': 0 } } | 
 | 239 |  | 
| Guido van Rossum | 1bc535d | 2007-05-15 18:46:22 +0000 | [diff] [blame] | 240 |     oldsigs = registerSignals(handle_signals, handle_signals, handle_signals) | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 241 |     try: | 
| Victor Stinner | ff40ecd | 2017-09-14 13:07:24 -0700 | [diff] [blame] | 242 |         support.run_unittest(ThreadSignals) | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 243 |     finally: | 
| Guido van Rossum | 1bc535d | 2007-05-15 18:46:22 +0000 | [diff] [blame] | 244 |         registerSignals(*oldsigs) | 
| Michael W. Hudson | 43220ea | 2004-08-03 14:37:14 +0000 | [diff] [blame] | 245 |  | 
 | 246 | if __name__ == '__main__': | 
 | 247 |     test_main() |