|
22 | 22 | import textwrap |
23 | 23 | import traceback |
24 | 24 | import warnings |
| 25 | +import ctypes |
25 | 26 |
|
26 | 27 | from unittest import mock |
27 | 28 | from test import lock_tests |
@@ -412,6 +413,39 @@ def run(self): |
412 | 413 | t.join() |
413 | 414 | # else the thread is still running, and we have no way to kill it |
414 | 415 |
|
| 416 | + @cpython_only |
| 417 | + @unittest.skipIf(not hasattr(signal, "pthread_kill"), "requires pthread_kill (Unix only)") |
| 418 | + def test_PyThreadState_SetAsyncExc_interrupts_sleep(self): |
| 419 | + set_async_exc = ctypes.pythonapi.PyThreadState_SetAsyncExc |
| 420 | + set_async_exc.argtypes = (ctypes.c_ulong, ctypes.py_object) |
| 421 | + |
| 422 | + class AsyncExc(Exception): |
| 423 | + pass |
| 424 | + |
| 425 | + exception = ctypes.py_object(AsyncExc) |
| 426 | + signal.signal(signal.SIGUSR1, lambda *_: None) |
| 427 | + worker_started = threading.Event() |
| 428 | + worker_finished = threading.Event() |
| 429 | + |
| 430 | + def worker(): |
| 431 | + tid = threading.get_ident() |
| 432 | + worker_started.set() |
| 433 | + try: |
| 434 | + time.sleep(10) # blocked in EINTR retry path |
| 435 | + except AsyncExc: |
| 436 | + worker_finished.set() |
| 437 | + |
| 438 | + t = threading.Thread(target=worker) |
| 439 | + t.start() |
| 440 | + worker_started.wait() |
| 441 | + time.sleep(0.2) |
| 442 | + result = set_async_exc(t.ident, exception) |
| 443 | + self.assertEqual(result, 1) |
| 444 | + signal.pthread_kill(t.ident, signal.SIGUSR1) |
| 445 | + worker_finished.wait(timeout=3) |
| 446 | + self.assertTrue(worker_finished.is_set()) |
| 447 | + t.join(timeout=3) |
| 448 | + |
415 | 449 | def test_limbo_cleanup(self): |
416 | 450 | # Issue 7481: Failure to start thread should cleanup the limbo map. |
417 | 451 | def fail_new_thread(*args, **kwargs): |
|
0 commit comments