PageRenderTime 153ms CodeModel.GetById 28ms RepoModel.GetById 7ms app.codeStats 0ms

/Lib/test/test_threadsignals.py

https://gitlab.com/unofficial-mirrors/cpython
Python | 227 lines | 149 code | 25 blank | 53 comment | 18 complexity | 826c3e1fcc0fda2a79be3b7af15afd6d MD5 | raw file
  1. """PyUnit testing that threads honor our signal semantics"""
  2. import unittest
  3. import signal
  4. import os
  5. import sys
  6. from test.support import run_unittest, import_module
  7. thread = import_module('_thread')
  8. import time
  9. if (sys.platform[:3] == 'win'):
  10. raise unittest.SkipTest("Can't test signal on %s" % sys.platform)
  11. process_pid = os.getpid()
  12. signalled_all=thread.allocate_lock()
  13. USING_PTHREAD_COND = (sys.thread_info.name == 'pthread'
  14. and sys.thread_info.lock == 'mutex+cond')
  15. def registerSignals(for_usr1, for_usr2, for_alrm):
  16. usr1 = signal.signal(signal.SIGUSR1, for_usr1)
  17. usr2 = signal.signal(signal.SIGUSR2, for_usr2)
  18. alrm = signal.signal(signal.SIGALRM, for_alrm)
  19. return usr1, usr2, alrm
  20. # The signal handler. Just note that the signal occurred and
  21. # from who.
  22. def handle_signals(sig,frame):
  23. signal_blackboard[sig]['tripped'] += 1
  24. signal_blackboard[sig]['tripped_by'] = thread.get_ident()
  25. # a function that will be spawned as a separate thread.
  26. def send_signals():
  27. os.kill(process_pid, signal.SIGUSR1)
  28. os.kill(process_pid, signal.SIGUSR2)
  29. signalled_all.release()
  30. class ThreadSignals(unittest.TestCase):
  31. def test_signals(self):
  32. # Test signal handling semantics of threads.
  33. # We spawn a thread, have the thread send two signals, and
  34. # wait for it to finish. Check that we got both signals
  35. # and that they were run by the main thread.
  36. signalled_all.acquire()
  37. self.spawnSignallingThread()
  38. signalled_all.acquire()
  39. # the signals that we asked the kernel to send
  40. # will come back, but we don't know when.
  41. # (it might even be after the thread exits
  42. # and might be out of order.) If we haven't seen
  43. # the signals yet, send yet another signal and
  44. # wait for it return.
  45. if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \
  46. or signal_blackboard[signal.SIGUSR2]['tripped'] == 0:
  47. signal.alarm(1)
  48. signal.pause()
  49. signal.alarm(0)
  50. self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1)
  51. self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'],
  52. thread.get_ident())
  53. self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped'], 1)
  54. self.assertEqual( signal_blackboard[signal.SIGUSR2]['tripped_by'],
  55. thread.get_ident())
  56. signalled_all.release()
  57. def spawnSignallingThread(self):
  58. thread.start_new_thread(send_signals, ())
  59. def alarm_interrupt(self, sig, frame):
  60. raise KeyboardInterrupt
  61. @unittest.skipIf(USING_PTHREAD_COND,
  62. 'POSIX condition variables cannot be interrupted')
  63. # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD
  64. @unittest.skipIf(sys.platform.startswith('openbsd'),
  65. 'lock cannot be interrupted on OpenBSD')
  66. def test_lock_acquire_interruption(self):
  67. # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
  68. # in a deadlock.
  69. # XXX this test can fail when the legacy (non-semaphore) implementation
  70. # of locks is used in thread_pthread.h, see issue #11223.
  71. oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
  72. try:
  73. lock = thread.allocate_lock()
  74. lock.acquire()
  75. signal.alarm(1)
  76. t1 = time.time()
  77. self.assertRaises(KeyboardInterrupt, lock.acquire, timeout=5)
  78. dt = time.time() - t1
  79. # Checking that KeyboardInterrupt was raised is not sufficient.
  80. # We want to assert that lock.acquire() was interrupted because
  81. # of the signal, not that the signal handler was called immediately
  82. # after timeout return of lock.acquire() (which can fool assertRaises).
  83. self.assertLess(dt, 3.0)
  84. finally:
  85. signal.signal(signal.SIGALRM, oldalrm)
  86. @unittest.skipIf(USING_PTHREAD_COND,
  87. 'POSIX condition variables cannot be interrupted')
  88. # Issue #20564: sem_timedwait() cannot be interrupted on OpenBSD
  89. @unittest.skipIf(sys.platform.startswith('openbsd'),
  90. 'lock cannot be interrupted on OpenBSD')
  91. def test_rlock_acquire_interruption(self):
  92. # Mimic receiving a SIGINT (KeyboardInterrupt) with SIGALRM while stuck
  93. # in a deadlock.
  94. # XXX this test can fail when the legacy (non-semaphore) implementation
  95. # of locks is used in thread_pthread.h, see issue #11223.
  96. oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
  97. try:
  98. rlock = thread.RLock()
  99. # For reentrant locks, the initial acquisition must be in another
  100. # thread.
  101. def other_thread():
  102. rlock.acquire()
  103. thread.start_new_thread(other_thread, ())
  104. # Wait until we can't acquire it without blocking...
  105. while rlock.acquire(blocking=False):
  106. rlock.release()
  107. time.sleep(0.01)
  108. signal.alarm(1)
  109. t1 = time.time()
  110. self.assertRaises(KeyboardInterrupt, rlock.acquire, timeout=5)
  111. dt = time.time() - t1
  112. # See rationale above in test_lock_acquire_interruption
  113. self.assertLess(dt, 3.0)
  114. finally:
  115. signal.signal(signal.SIGALRM, oldalrm)
  116. def acquire_retries_on_intr(self, lock):
  117. self.sig_recvd = False
  118. def my_handler(signal, frame):
  119. self.sig_recvd = True
  120. old_handler = signal.signal(signal.SIGUSR1, my_handler)
  121. try:
  122. def other_thread():
  123. # Acquire the lock in a non-main thread, so this test works for
  124. # RLocks.
  125. lock.acquire()
  126. # Wait until the main thread is blocked in the lock acquire, and
  127. # then wake it up with this.
  128. time.sleep(0.5)
  129. os.kill(process_pid, signal.SIGUSR1)
  130. # Let the main thread take the interrupt, handle it, and retry
  131. # the lock acquisition. Then we'll let it run.
  132. time.sleep(0.5)
  133. lock.release()
  134. thread.start_new_thread(other_thread, ())
  135. # Wait until we can't acquire it without blocking...
  136. while lock.acquire(blocking=False):
  137. lock.release()
  138. time.sleep(0.01)
  139. result = lock.acquire() # Block while we receive a signal.
  140. self.assertTrue(self.sig_recvd)
  141. self.assertTrue(result)
  142. finally:
  143. signal.signal(signal.SIGUSR1, old_handler)
  144. def test_lock_acquire_retries_on_intr(self):
  145. self.acquire_retries_on_intr(thread.allocate_lock())
  146. def test_rlock_acquire_retries_on_intr(self):
  147. self.acquire_retries_on_intr(thread.RLock())
  148. def test_interrupted_timed_acquire(self):
  149. # Test to make sure we recompute lock acquisition timeouts when we
  150. # receive a signal. Check this by repeatedly interrupting a lock
  151. # acquire in the main thread, and make sure that the lock acquire times
  152. # out after the right amount of time.
  153. # NOTE: this test only behaves as expected if C signals get delivered
  154. # to the main thread. Otherwise lock.acquire() itself doesn't get
  155. # interrupted and the test trivially succeeds.
  156. self.start = None
  157. self.end = None
  158. self.sigs_recvd = 0
  159. done = thread.allocate_lock()
  160. done.acquire()
  161. lock = thread.allocate_lock()
  162. lock.acquire()
  163. def my_handler(signum, frame):
  164. self.sigs_recvd += 1
  165. old_handler = signal.signal(signal.SIGUSR1, my_handler)
  166. try:
  167. def timed_acquire():
  168. self.start = time.time()
  169. lock.acquire(timeout=0.5)
  170. self.end = time.time()
  171. def send_signals():
  172. for _ in range(40):
  173. time.sleep(0.02)
  174. os.kill(process_pid, signal.SIGUSR1)
  175. done.release()
  176. # Send the signals from the non-main thread, since the main thread
  177. # is the only one that can process signals.
  178. thread.start_new_thread(send_signals, ())
  179. timed_acquire()
  180. # Wait for thread to finish
  181. done.acquire()
  182. # This allows for some timing and scheduling imprecision
  183. self.assertLess(self.end - self.start, 2.0)
  184. self.assertGreater(self.end - self.start, 0.3)
  185. # If the signal is received several times before PyErr_CheckSignals()
  186. # is called, the handler will get called less than 40 times. Just
  187. # check it's been called at least once.
  188. self.assertGreater(self.sigs_recvd, 0)
  189. finally:
  190. signal.signal(signal.SIGUSR1, old_handler)
  191. def test_main():
  192. global signal_blackboard
  193. signal_blackboard = { signal.SIGUSR1 : {'tripped': 0, 'tripped_by': 0 },
  194. signal.SIGUSR2 : {'tripped': 0, 'tripped_by': 0 },
  195. signal.SIGALRM : {'tripped': 0, 'tripped_by': 0 } }
  196. oldsigs = registerSignals(handle_signals, handle_signals, handle_signals)
  197. try:
  198. run_unittest(ThreadSignals)
  199. finally:
  200. registerSignals(*oldsigs)
  201. if __name__ == '__main__':
  202. test_main()