blob: aa63955e5fdd059366523e335674ed06b0ec5c47 [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():
33 os.kill(process_pid, signal.SIGUSR1)
34 os.kill(process_pid, signal.SIGUSR2)
35 signalled_all.release()
36
37class ThreadSignals(unittest.TestCase):
Antoine Pitrou810023d2010-12-15 22:59:16 +000038
Michael W. Hudson43220ea2004-08-03 14:37:14 +000039 def test_signals(self):
Antoine Pitrou810023d2010-12-15 22:59:16 +000040 # 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.
Michael W. Hudson43220ea2004-08-03 14:37:14 +000044 signalled_all.acquire()
45 self.spawnSignallingThread()
46 signalled_all.acquire()
47 # the signals that we asked the kernel to send
48 # will come back, but we don't know when.
49 # (it might even be after the thread exits
50 # and might be out of order.) If we haven't seen
51 # the signals yet, send yet another signal and
52 # wait for it return.
Thomas Wouters00ee7ba2006-08-21 19:07:27 +000053 if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \
Michael W. Hudson43220ea2004-08-03 14:37:14 +000054 or signal_blackboard[signal.SIGUSR2]['tripped'] == 0:
Tim Peters6db15d72004-08-04 02:36:18 +000055 signal.alarm(1)
56 signal.pause()
57 signal.alarm(0)
Michael W. Hudson43220ea2004-08-03 14:37:14 +000058
59 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000060 self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000061 thread.get_ident())
62 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped'], 1)
Tim Peters6db15d72004-08-04 02:36:18 +000063 self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped_by'],
Michael W. Hudson43220ea2004-08-03 14:37:14 +000064 thread.get_ident())
Michael W. Hudson574a2512004-08-04 14:22:56 +000065 signalled_all.release()
Michael W. Hudson43220ea2004-08-03 14:37:14 +000066
67 def spawnSignallingThread(self):
68 thread.start_new_thread(send_signals, ())
Tim Peters6db15d72004-08-04 02:36:18 +000069
Antoine Pitrou810023d2010-12-15 22:59:16 +000070 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
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000179
180def test_main():
Michael W. Hudson574a2512004-08-04 14:22:56 +0000181 global signal_blackboard
Tim Petersd1b78272004-08-07 06:03:09 +0000182
Michael W. Hudson574a2512004-08-04 14:22:56 +0000183 signal_blackboard = { signal.SIGUSR1 : {'tripped': 0, 'tripped_by': 0 },
184 signal.SIGUSR2 : {'tripped': 0, 'tripped_by': 0 },
185 signal.SIGALRM : {'tripped': 0, 'tripped_by': 0 } }
186
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000187 oldsigs = registerSignals(handle_signals, handle_signals, handle_signals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000188 try:
Michael W. Hudson574a2512004-08-04 14:22:56 +0000189 run_unittest(ThreadSignals)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000190 finally:
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000191 registerSignals(*oldsigs)
Michael W. Hudson43220ea2004-08-03 14:37:14 +0000192
193if __name__ == '__main__':
194 test_main()