Helper for tests that use os.fork() Use in a with statement: with self.fork(target=lambda: print('in child')) as proc: self.assertTrue(proc.pid) # Child process was started
(
self, target: Callable, timeout: float = 60
)
| 919 | |
| 920 | @contextmanager |
| 921 | def fork( |
| 922 | self, target: Callable, timeout: float = 60 |
| 923 | ) -> Generator[multiprocessing.Process, None, None]: |
| 924 | """Helper for tests that use os.fork() |
| 925 | |
| 926 | Use in a with statement: |
| 927 | |
| 928 | with self.fork(target=lambda: print('in child')) as proc: |
| 929 | self.assertTrue(proc.pid) # Child process was started |
| 930 | """ |
| 931 | |
| 932 | def _print_threads(*args: object) -> None: |
| 933 | if _print_threads.called: # type:ignore[attr-defined] |
| 934 | return |
| 935 | _print_threads.called = True # type:ignore[attr-defined] |
| 936 | print_thread_tracebacks() |
| 937 | |
| 938 | _print_threads.called = False # type:ignore[attr-defined] |
| 939 | |
| 940 | def _target() -> None: |
| 941 | signal.signal(signal.SIGUSR1, _print_threads) |
| 942 | try: |
| 943 | target() |
| 944 | except Exception as exc: |
| 945 | sys.stderr.write(f"Child process failed with: {exc}\n") |
| 946 | _print_threads() |
| 947 | # Sleep for a while to let the parent attach via GDB. |
| 948 | time.sleep(2 * timeout) |
| 949 | raise |
| 950 | |
| 951 | ctx = multiprocessing.get_context("fork") |
| 952 | proc = ctx.Process(target=_target) |
| 953 | proc.start() |
| 954 | try: |
| 955 | yield proc # type: ignore |
| 956 | finally: |
| 957 | proc.join(timeout) |
| 958 | pid = proc.pid |
| 959 | assert pid |
| 960 | if proc.exitcode is None: |
| 961 | # gdb to get C-level tracebacks |
| 962 | print_thread_stacks(pid) |
| 963 | # If it failed, SIGUSR1 to get thread tracebacks. |
| 964 | os.kill(pid, signal.SIGUSR1) |
| 965 | proc.join(5) |
| 966 | if proc.exitcode is None: |
| 967 | # SIGINT to get main thread traceback in case SIGUSR1 didn't work. |
| 968 | os.kill(pid, signal.SIGINT) |
| 969 | proc.join(5) |
| 970 | if proc.exitcode is None: |
| 971 | # SIGKILL in case SIGINT didn't work. |
| 972 | proc.kill() |
| 973 | proc.join(1) |
| 974 | self.fail(f"child timed out after {timeout}s (see traceback in logs): deadlock?") |
| 975 | self.assertEqual(proc.exitcode, 0) |
| 976 | |
| 977 | @classmethod |
| 978 | def _unmanaged_async_mongo_client( |