Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:54

0001 import multiprocessing
0002 import sys
0003 import threading
0004 import time
0005 
0006 import pytest
0007 
0008 import env
0009 from pybind11_tests import gil_scoped as m
0010 
0011 
0012 class ExtendedVirtClass(m.VirtClass):
0013     def virtual_func(self):
0014         pass
0015 
0016     def pure_virtual_func(self):
0017         pass
0018 
0019 
0020 def test_callback_py_obj():
0021     m.test_callback_py_obj(lambda: None)
0022 
0023 
0024 def test_callback_std_func():
0025     m.test_callback_std_func(lambda: None)
0026 
0027 
0028 def test_callback_virtual_func():
0029     extended = ExtendedVirtClass()
0030     m.test_callback_virtual_func(extended)
0031 
0032 
0033 def test_callback_pure_virtual_func():
0034     extended = ExtendedVirtClass()
0035     m.test_callback_pure_virtual_func(extended)
0036 
0037 
0038 def test_cross_module_gil_released():
0039     """Makes sure that the GIL can be acquired by another module from a GIL-released state."""
0040     m.test_cross_module_gil_released()  # Should not raise a SIGSEGV
0041 
0042 
0043 def test_cross_module_gil_acquired():
0044     """Makes sure that the GIL can be acquired by another module from a GIL-acquired state."""
0045     m.test_cross_module_gil_acquired()  # Should not raise a SIGSEGV
0046 
0047 
0048 def test_cross_module_gil_inner_custom_released():
0049     """Makes sure that the GIL can be acquired/released by another module
0050     from a GIL-released state using custom locking logic."""
0051     m.test_cross_module_gil_inner_custom_released()
0052 
0053 
0054 def test_cross_module_gil_inner_custom_acquired():
0055     """Makes sure that the GIL can be acquired/acquired by another module
0056     from a GIL-acquired state using custom locking logic."""
0057     m.test_cross_module_gil_inner_custom_acquired()
0058 
0059 
0060 def test_cross_module_gil_inner_pybind11_released():
0061     """Makes sure that the GIL can be acquired/released by another module
0062     from a GIL-released state using pybind11 locking logic."""
0063     m.test_cross_module_gil_inner_pybind11_released()
0064 
0065 
0066 def test_cross_module_gil_inner_pybind11_acquired():
0067     """Makes sure that the GIL can be acquired/acquired by another module
0068     from a GIL-acquired state using pybind11 locking logic."""
0069     m.test_cross_module_gil_inner_pybind11_acquired()
0070 
0071 
0072 def test_cross_module_gil_nested_custom_released():
0073     """Makes sure that the GIL can be nested acquired/released by another module
0074     from a GIL-released state using custom locking logic."""
0075     m.test_cross_module_gil_nested_custom_released()
0076 
0077 
0078 def test_cross_module_gil_nested_custom_acquired():
0079     """Makes sure that the GIL can be nested acquired/acquired by another module
0080     from a GIL-acquired state using custom locking logic."""
0081     m.test_cross_module_gil_nested_custom_acquired()
0082 
0083 
0084 def test_cross_module_gil_nested_pybind11_released():
0085     """Makes sure that the GIL can be nested acquired/released by another module
0086     from a GIL-released state using pybind11 locking logic."""
0087     m.test_cross_module_gil_nested_pybind11_released()
0088 
0089 
0090 def test_cross_module_gil_nested_pybind11_acquired():
0091     """Makes sure that the GIL can be nested acquired/acquired by another module
0092     from a GIL-acquired state using pybind11 locking logic."""
0093     m.test_cross_module_gil_nested_pybind11_acquired()
0094 
0095 
0096 def test_release_acquire():
0097     assert m.test_release_acquire(0xAB) == "171"
0098 
0099 
0100 def test_nested_acquire():
0101     assert m.test_nested_acquire(0xAB) == "171"
0102 
0103 
0104 def test_multi_acquire_release_cross_module():
0105     for bits in range(16 * 8):
0106         internals_ids = m.test_multi_acquire_release_cross_module(bits)
0107         assert len(internals_ids) == 2 if bits % 8 else 1
0108 
0109 
0110 # Intentionally putting human review in the loop here, to guard against accidents.
0111 VARS_BEFORE_ALL_BASIC_TESTS = dict(vars())  # Make a copy of the dict (critical).
0112 ALL_BASIC_TESTS = (
0113     test_callback_py_obj,
0114     test_callback_std_func,
0115     test_callback_virtual_func,
0116     test_callback_pure_virtual_func,
0117     test_cross_module_gil_released,
0118     test_cross_module_gil_acquired,
0119     test_cross_module_gil_inner_custom_released,
0120     test_cross_module_gil_inner_custom_acquired,
0121     test_cross_module_gil_inner_pybind11_released,
0122     test_cross_module_gil_inner_pybind11_acquired,
0123     test_cross_module_gil_nested_custom_released,
0124     test_cross_module_gil_nested_custom_acquired,
0125     test_cross_module_gil_nested_pybind11_released,
0126     test_cross_module_gil_nested_pybind11_acquired,
0127     test_release_acquire,
0128     test_nested_acquire,
0129     test_multi_acquire_release_cross_module,
0130 )
0131 
0132 
0133 def test_all_basic_tests_completeness():
0134     num_found = 0
0135     for key, value in VARS_BEFORE_ALL_BASIC_TESTS.items():
0136         if not key.startswith("test_"):
0137             continue
0138         assert value in ALL_BASIC_TESTS
0139         num_found += 1
0140     assert len(ALL_BASIC_TESTS) == num_found
0141 
0142 
0143 def _intentional_deadlock():
0144     m.intentional_deadlock()
0145 
0146 
0147 ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_deadlock,)
0148 
0149 
0150 def _run_in_process(target, *args, **kwargs):
0151     if len(args) == 0:
0152         test_fn = target
0153     else:
0154         test_fn = args[0]
0155     # Do not need to wait much, 10s should be more than enough.
0156     timeout = 0.1 if test_fn is _intentional_deadlock else 10
0157     process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
0158     process.daemon = True
0159     try:
0160         t_start = time.time()
0161         process.start()
0162         if timeout >= 100:  # For debugging.
0163             print(
0164                 "\nprocess.pid STARTED", process.pid, (sys.argv, target, args, kwargs)
0165             )
0166             print(f"COPY-PASTE-THIS: gdb {sys.argv[0]} -p {process.pid}", flush=True)
0167         process.join(timeout=timeout)
0168         if timeout >= 100:
0169             print("\nprocess.pid JOINED", process.pid, flush=True)
0170         t_delta = time.time() - t_start
0171         if process.exitcode == 66 and m.defined_THREAD_SANITIZER:  # Issue #2754
0172             # WOULD-BE-NICE-TO-HAVE: Check that the message below is actually in the output.
0173             # Maybe this could work:
0174             # https://gist.github.com/alexeygrigorev/01ce847f2e721b513b42ea4a6c96905e
0175             pytest.skip(
0176                 "ThreadSanitizer: starting new threads after multi-threaded fork is not supported."
0177             )
0178         elif test_fn is _intentional_deadlock:
0179             assert process.exitcode is None
0180             return 0
0181         elif process.exitcode is None:
0182             assert t_delta > 0.9 * timeout
0183             msg = "DEADLOCK, most likely, exactly what this test is meant to detect."
0184             if env.PYPY and env.WIN:
0185                 pytest.skip(msg)
0186             raise RuntimeError(msg)
0187         return process.exitcode
0188     finally:
0189         if process.is_alive():
0190             process.terminate()
0191 
0192 
0193 def _run_in_threads(test_fn, num_threads, parallel):
0194     threads = []
0195     for _ in range(num_threads):
0196         thread = threading.Thread(target=test_fn)
0197         thread.daemon = True
0198         thread.start()
0199         if parallel:
0200             threads.append(thread)
0201         else:
0202             thread.join()
0203     for thread in threads:
0204         thread.join()
0205 
0206 
0207 # TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9
0208 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
0209 def test_run_in_process_one_thread(test_fn):
0210     """Makes sure there is no GIL deadlock when running in a thread.
0211 
0212     It runs in a separate process to be able to stop and assert if it deadlocks.
0213     """
0214     assert _run_in_process(_run_in_threads, test_fn, num_threads=1, parallel=False) == 0
0215 
0216 
0217 # TODO: FIXME on macOS Python 3.9
0218 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
0219 def test_run_in_process_multiple_threads_parallel(test_fn):
0220     """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
0221 
0222     It runs in a separate process to be able to stop and assert if it deadlocks.
0223     """
0224     assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=True) == 0
0225 
0226 
0227 # TODO: FIXME on macOS Python 3.9
0228 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
0229 def test_run_in_process_multiple_threads_sequential(test_fn):
0230     """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
0231 
0232     It runs in a separate process to be able to stop and assert if it deadlocks.
0233     """
0234     assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=False) == 0
0235 
0236 
0237 # TODO: FIXME on macOS Python 3.9
0238 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
0239 def test_run_in_process_direct(test_fn):
0240     """Makes sure there is no GIL deadlock when using processes.
0241 
0242     This test is for completion, but it was never an issue.
0243     """
0244     assert _run_in_process(test_fn) == 0