@@ -2415,6 +2415,93 @@ assert errors == ["first initialization failed"]
24152415assert failure_state.attempts == 2
24162416assert other_importer.load() == 42
24172417
2418+ [case testFailedConcurrentCircularNativeImportRefreshesCache]
2419+ # separate: [(["native.py", "other_a.py", "other_b.py"], "testgroup")]
2420+ pass
2421+
2422+ [file import_sync.py]
2423+ from threading import Event
2424+
2425+ a_started = Event()
2426+ b_started = Event()
2427+ a_importing_b = Event()
2428+ b_waiting_for_driver = Event()
2429+ b_may_import_a = Event()
2430+
2431+ [file failure_state.py]
2432+ attempts = 0
2433+
2434+ [file other_a.py]
2435+ import failure_state
2436+
2437+ failure_state.attempts += 1
2438+ if failure_state.attempts == 1:
2439+ import import_sync
2440+
2441+ import_sync.a_started.set()
2442+ assert import_sync.b_started.wait(timeout=5)
2443+ import_sync.a_importing_b.set()
2444+ import other_b
2445+ raise RuntimeError("first initialization failed")
2446+
2447+ value = 42
2448+
2449+ [file other_b.py]
2450+ import import_sync
2451+
2452+ import_sync.b_started.set()
2453+ assert import_sync.a_started.wait(timeout=5)
2454+ assert import_sync.a_importing_b.wait(timeout=5)
2455+ import_sync.b_waiting_for_driver.set()
2456+ assert import_sync.b_may_import_a.wait(timeout=5)
2457+ import other_a
2458+
2459+ saw_partial_a = not hasattr(other_a, "value")
2460+
2461+ def get_a_value() -> int:
2462+ return other_a.value
2463+
2464+ [file driver.py]
2465+ from concurrent.futures import ThreadPoolExecutor
2466+ import importlib
2467+ from time import monotonic, sleep
2468+
2469+ from testutil import assertRaises
2470+
2471+ import failure_state
2472+ import import_sync
2473+ import native # Preload the compilation group's shared library.
2474+
2475+ with ThreadPoolExecutor(max_workers=2) as executor:
2476+ future_a = executor.submit(importlib.import_module, "other_a")
2477+ assert import_sync.a_started.wait(timeout=5)
2478+ future_b = executor.submit(importlib.import_module, "other_b")
2479+ assert import_sync.a_importing_b.wait(timeout=5)
2480+ assert import_sync.b_waiting_for_driver.wait(timeout=5)
2481+
2482+ # Wait until other_a is blocked on other_b's module lock. Letting other_b
2483+ # import other_a then deterministically closes the lock cycle and returns
2484+ # other_a's partial module.
2485+ bootstrap = importlib.import_module("importlib._bootstrap")
2486+ lock = bootstrap._get_module_lock("other_b")
2487+ deadline = monotonic() + 5
2488+ while not lock.waiters and monotonic() < deadline:
2489+ sleep(0.001)
2490+ has_waiter = bool(lock.waiters)
2491+ import_sync.b_may_import_a.set()
2492+ assert has_waiter
2493+
2494+ with assertRaises(RuntimeError, "first initialization failed"):
2495+ future_a.result(timeout=10)
2496+ other_b = future_b.result(timeout=10)
2497+
2498+ assert failure_state.attempts == 1
2499+ assert other_b.saw_partial_a
2500+ other_a = importlib.import_module("other_a")
2501+ assert failure_state.attempts == 2
2502+ assert other_a.value == 42
2503+ assert other_b.get_a_value() == 42
2504+
24182505[case testConcurrentCircularNativeImports]
24192506# separate: [(["other_a.py", "other_b.py"], "testgroup")]
24202507pass
@@ -2457,6 +2544,33 @@ with ThreadPoolExecutor(max_workers=2) as executor:
24572544assert other_a.other_value == "b"
24582545assert other_b.other_value == "a"
24592546
2547+ [case testNativeModuleReimportBehavior]
2548+ value = 42
2549+
2550+ def get_value() -> int:
2551+ return value
2552+
2553+ [file driver.py]
2554+ import importlib
2555+ import sys
2556+
2557+ from testutil import assertRaises
2558+
2559+ import native
2560+
2561+ first = native
2562+ assert first.get_value() == 42
2563+ del sys.modules["native"]
2564+ if hasattr(sys, "_is_gil_enabled") and not sys._is_gil_enabled():
2565+ with assertRaises(ImportError, "native module 'native' does not support reinitialization"):
2566+ importlib.import_module("native")
2567+ assert "native" not in sys.modules
2568+ else:
2569+ # Legacy single-phase initialization returns the original module object.
2570+ second = importlib.import_module("native")
2571+ assert second is first
2572+ assert first.get_value() == 42
2573+
24602574[case testTopLevelThreadImportsNativeModuleFromSameGroup]
24612575# separate: [(["native.py", "other_target.py"], "testgroup")]
24622576from threading import Thread
0 commit comments