PageRenderTime 84ms CodeModel.GetById 20ms RepoModel.GetById 4ms app.codeStats 0ms

/kbe/src/lib/python/Lib/test/test_threadsignals.py

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