|
| 1 | +"""This test checks for correct fork() behavior. |
| 2 | +""" |
| 3 | + |
| 4 | +import _imp as imp |
| 5 | +import os |
| 6 | +import signal |
| 7 | +import sys |
| 8 | +import threading |
| 9 | +import time |
| 10 | +import unittest |
| 11 | + |
| 12 | +from test.fork_wait import ForkWait |
| 13 | +from test import support |
| 14 | + |
| 15 | + |
| 16 | +# Skip test if fork does not exist. |
| 17 | +if not support.has_fork_support: |
| 18 | + raise unittest.SkipTest("test module requires working os.fork") |
| 19 | + |
| 20 | + |
| 21 | +class ForkTest(ForkWait): |
| 22 | + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: process 44587 exited with code 1, but exit code 42 is expected |
| 23 | + def test_threaded_import_lock_fork(self): |
| 24 | + """Check fork() in main thread works while a subthread is doing an import""" |
| 25 | + import_started = threading.Event() |
| 26 | + fake_module_name = "fake test module" |
| 27 | + partial_module = "partial" |
| 28 | + complete_module = "complete" |
| 29 | + def importer(): |
| 30 | + imp.acquire_lock() |
| 31 | + sys.modules[fake_module_name] = partial_module |
| 32 | + import_started.set() |
| 33 | + time.sleep(0.01) # Give the other thread time to try and acquire. |
| 34 | + sys.modules[fake_module_name] = complete_module |
| 35 | + imp.release_lock() |
| 36 | + t = threading.Thread(target=importer) |
| 37 | + t.start() |
| 38 | + import_started.wait() |
| 39 | + exitcode = 42 |
| 40 | + pid = os.fork() |
| 41 | + try: |
| 42 | + # PyOS_BeforeFork should have waited for the import to complete |
| 43 | + # before forking, so the child can recreate the import lock |
| 44 | + # correctly, but also won't see a partially initialised module |
| 45 | + if not pid: |
| 46 | + m = __import__(fake_module_name) |
| 47 | + if m == complete_module: |
| 48 | + os._exit(exitcode) |
| 49 | + else: |
| 50 | + if support.verbose > 1: |
| 51 | + print("Child encountered partial module") |
| 52 | + os._exit(1) |
| 53 | + else: |
| 54 | + t.join() |
| 55 | + # Exitcode 1 means the child got a partial module (bad.) No |
| 56 | + # exitcode (but a hang, which manifests as 'got pid 0') |
| 57 | + # means the child deadlocked (also bad.) |
| 58 | + self.wait_impl(pid, exitcode=exitcode) |
| 59 | + finally: |
| 60 | + try: |
| 61 | + os.kill(pid, signal.SIGKILL) |
| 62 | + except OSError: |
| 63 | + pass |
| 64 | + |
| 65 | + |
| 66 | + def test_nested_import_lock_fork(self): |
| 67 | + """Check fork() in main thread works while the main thread is doing an import""" |
| 68 | + exitcode = 42 |
| 69 | + # Issue 9573: this used to trigger RuntimeError in the child process |
| 70 | + def fork_with_import_lock(level): |
| 71 | + release = 0 |
| 72 | + in_child = False |
| 73 | + try: |
| 74 | + try: |
| 75 | + for i in range(level): |
| 76 | + imp.acquire_lock() |
| 77 | + release += 1 |
| 78 | + pid = os.fork() |
| 79 | + in_child = not pid |
| 80 | + finally: |
| 81 | + for i in range(release): |
| 82 | + imp.release_lock() |
| 83 | + except RuntimeError: |
| 84 | + if in_child: |
| 85 | + if support.verbose > 1: |
| 86 | + print("RuntimeError in child") |
| 87 | + os._exit(1) |
| 88 | + raise |
| 89 | + if in_child: |
| 90 | + os._exit(exitcode) |
| 91 | + self.wait_impl(pid, exitcode=exitcode) |
| 92 | + |
| 93 | + # Check this works with various levels of nested |
| 94 | + # import in the main thread |
| 95 | + for level in range(5): |
| 96 | + fork_with_import_lock(level) |
| 97 | + |
| 98 | + |
| 99 | +def tearDownModule(): |
| 100 | + support.reap_children() |
| 101 | + |
| 102 | +if __name__ == "__main__": |
| 103 | + unittest.main() |
0 commit comments