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()
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()
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
0111 VARS_BEFORE_ALL_BASIC_TESTS = dict(vars())
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
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:
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:
0172
0173
0174
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
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
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
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
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