"""Minimal reproduction of multiprocessing Manager + fork failure.""" import multiprocessing import os import sys import time import traceback import pytest pytestmark = pytest.mark.skipif(not hasattr(os, "fork"), reason="requires os.fork") def test_basic_manager(): """Test Manager without fork - does it work at all?""" print("=== Test 1: Basic Manager (no fork) ===") ctx = multiprocessing.get_context("fork") manager = ctx.Manager() try: ev = manager.Event() print(f" Event created: {ev}") ev.set() print(f" Event set, is_set={ev.is_set()}") assert ev.is_set() print(" PASS") finally: manager.shutdown() def test_manager_with_process(): """Test Manager shared between parent and child process.""" print("\n=== Test 2: Manager with forked child ===") ctx = multiprocessing.get_context("fork") manager = ctx.Manager() try: result = manager.Value("i", 0) ev = manager.Event() def child_fn(): try: ev.set() result.value = 42 except Exception as e: print(f" CHILD ERROR: {e}", file=sys.stderr) traceback.print_exc() sys.exit(1) print(f" Starting child process...") process = ctx.Process(target=child_fn) process.start() print(f" Waiting for child (pid={process.pid})...") process.join(timeout=10) if process.exitcode != 0: print(f" FAIL: child exited with code {process.exitcode}") return False print(f" Child done. result={result.value}, event={ev.is_set()}") assert result.value == 42 assert ev.is_set() print(" PASS") return True finally: manager.shutdown() def test_manager_server_alive_after_fork(): """Test that Manager server survives after forking a child.""" print("\n=== Test 3: Manager server alive after fork ===") ctx = multiprocessing.get_context("fork") manager = ctx.Manager() try: ev = manager.Event() # Fork a child that does nothing with the manager pid = os.fork() if pid == 0: # Child - exit immediately os._exit(0) # Parent - wait for child os.waitpid(pid, 0) # Now try to use the manager in the parent print(f" After fork, trying to use Manager in parent...") ev.set() print(f" ev.is_set() = {ev.is_set()}") assert ev.is_set() print(" PASS") return True finally: manager.shutdown() def test_manager_server_alive_after_fork_with_child_usage(): """Test that Manager server survives when child also uses it.""" print("\n=== Test 4: Manager server alive after fork + child usage ===") ctx = multiprocessing.get_context("fork") manager = ctx.Manager() try: child_ev = manager.Event() parent_ev = manager.Event() def child_fn(): try: child_ev.set() except Exception as e: print(f" CHILD ERROR: {e}", file=sys.stderr) traceback.print_exc() sys.exit(1) process = ctx.Process(target=child_fn) process.start() process.join(timeout=10) if process.exitcode != 0: print(f" FAIL: child exited with code {process.exitcode}") return False # Now use manager in parent AFTER child is done print(f" Child done. Trying parent usage...") parent_ev.set() print(f" child_ev={child_ev.is_set()}, parent_ev={parent_ev.is_set()}") assert child_ev.is_set() assert parent_ev.is_set() print(" PASS") return True finally: manager.shutdown() if __name__ == "__main__": test_basic_manager() passed = 0 total = 10 for i in range(total): print(f"\n--- Iteration {i + 1}/{total} ---") ok = True ok = ok and test_manager_with_process() ok = ok and test_manager_server_alive_after_fork() ok = ok and test_manager_server_alive_after_fork_with_child_usage() if ok: passed += 1 else: print(f" FAILED on iteration {i + 1}") print(f"\n=== Results: {passed}/{total} passed ===") sys.exit(0 if passed == total else 1)