μHAL (v2.8.17)
Part of the IPbus software repository
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
test_gil_scoped.py
Go to the documentation of this file.
1import multiprocessing
2import sys
3import threading
4import time
5
6import pytest
7
8import env
9from pybind11_tests import gil_scoped as m
10
11
12class ExtendedVirtClass(m.VirtClass):
13 def virtual_func(self):
14 pass
15
17 pass
18
19
21 m.test_callback_py_obj(lambda: None)
22
23
25 m.test_callback_std_func(lambda: None)
26
27
29 extended = ExtendedVirtClass()
30 m.test_callback_virtual_func(extended)
31
32
34 extended = ExtendedVirtClass()
35 m.test_callback_pure_virtual_func(extended)
36
37
39 """Makes sure that the GIL can be acquired by another module from a GIL-released state."""
40 m.test_cross_module_gil_released() # Should not raise a SIGSEGV
41
42
44 """Makes sure that the GIL can be acquired by another module from a GIL-acquired state."""
45 m.test_cross_module_gil_acquired() # Should not raise a SIGSEGV
46
47
49 """Makes sure that the GIL can be acquired/released by another module
50 from a GIL-released state using custom locking logic."""
51 m.test_cross_module_gil_inner_custom_released()
52
53
55 """Makes sure that the GIL can be acquired/acquired by another module
56 from a GIL-acquired state using custom locking logic."""
57 m.test_cross_module_gil_inner_custom_acquired()
58
59
61 """Makes sure that the GIL can be acquired/released by another module
62 from a GIL-released state using pybind11 locking logic."""
63 m.test_cross_module_gil_inner_pybind11_released()
64
65
67 """Makes sure that the GIL can be acquired/acquired by another module
68 from a GIL-acquired state using pybind11 locking logic."""
69 m.test_cross_module_gil_inner_pybind11_acquired()
70
71
73 """Makes sure that the GIL can be nested acquired/released by another module
74 from a GIL-released state using custom locking logic."""
75 m.test_cross_module_gil_nested_custom_released()
76
77
79 """Makes sure that the GIL can be nested acquired/acquired by another module
80 from a GIL-acquired state using custom locking logic."""
81 m.test_cross_module_gil_nested_custom_acquired()
82
83
85 """Makes sure that the GIL can be nested acquired/released by another module
86 from a GIL-released state using pybind11 locking logic."""
87 m.test_cross_module_gil_nested_pybind11_released()
88
89
91 """Makes sure that the GIL can be nested acquired/acquired by another module
92 from a GIL-acquired state using pybind11 locking logic."""
93 m.test_cross_module_gil_nested_pybind11_acquired()
94
95
97 assert m.test_release_acquire(0xAB) == "171"
98
99
101 assert m.test_nested_acquire(0xAB) == "171"
102
103
105 for bits in range(16 * 8):
106 internals_ids = m.test_multi_acquire_release_cross_module(bits)
107 assert len(internals_ids) == 2 if bits % 8 else 1
108
109
110# Intentionally putting human review in the loop here, to guard against accidents.
111VARS_BEFORE_ALL_BASIC_TESTS = dict(vars()) # Make a copy of the dict (critical).
112ALL_BASIC_TESTS = (
113 test_callback_py_obj,
114 test_callback_std_func,
115 test_callback_virtual_func,
116 test_callback_pure_virtual_func,
117 test_cross_module_gil_released,
118 test_cross_module_gil_acquired,
119 test_cross_module_gil_inner_custom_released,
120 test_cross_module_gil_inner_custom_acquired,
121 test_cross_module_gil_inner_pybind11_released,
122 test_cross_module_gil_inner_pybind11_acquired,
123 test_cross_module_gil_nested_custom_released,
124 test_cross_module_gil_nested_custom_acquired,
125 test_cross_module_gil_nested_pybind11_released,
126 test_cross_module_gil_nested_pybind11_acquired,
127 test_release_acquire,
128 test_nested_acquire,
129 test_multi_acquire_release_cross_module,
130)
131
132
134 num_found = 0
135 for key, value in VARS_BEFORE_ALL_BASIC_TESTS.items():
136 if not key.startswith("test_"):
137 continue
138 assert value in ALL_BASIC_TESTS
139 num_found += 1
140 assert len(ALL_BASIC_TESTS) == num_found
141
142
144 m.intentional_deadlock()
145
146
147ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_deadlock,)
148
149
150def _run_in_process(target, *args, **kwargs):
151 if len(args) == 0:
152 test_fn = target
153 else:
154 test_fn = args[0]
155 # Do not need to wait much, 10s should be more than enough.
156 timeout = 0.1 if test_fn is _intentional_deadlock else 10
157 process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
158 process.daemon = True
159 try:
160 t_start = time.time()
161 process.start()
162 if timeout >= 100: # For debugging.
163 print(
164 "\nprocess.pid STARTED", process.pid, (sys.argv, target, args, kwargs)
165 )
166 print(f"COPY-PASTE-THIS: gdb {sys.argv[0]} -p {process.pid}", flush=True)
167 process.join(timeout=timeout)
168 if timeout >= 100:
169 print("\nprocess.pid JOINED", process.pid, flush=True)
170 t_delta = time.time() - t_start
171 if process.exitcode == 66 and m.defined_THREAD_SANITIZER: # Issue #2754
172 # WOULD-BE-NICE-TO-HAVE: Check that the message below is actually in the output.
173 # Maybe this could work:
174 # https://gist.github.com/alexeygrigorev/01ce847f2e721b513b42ea4a6c96905e
175 pytest.skip(
176 "ThreadSanitizer: starting new threads after multi-threaded fork is not supported."
177 )
178 elif test_fn is _intentional_deadlock:
179 assert process.exitcode is None
180 return 0
181 elif process.exitcode is None:
182 assert t_delta > 0.9 * timeout
183 msg = "DEADLOCK, most likely, exactly what this test is meant to detect."
184 if env.PYPY and env.WIN:
185 pytest.skip(msg)
186 raise RuntimeError(msg)
187 return process.exitcode
188 finally:
189 if process.is_alive():
190 process.terminate()
191
192
193def _run_in_threads(test_fn, num_threads, parallel):
194 threads = []
195 for _ in range(num_threads):
196 thread = threading.Thread(target=test_fn)
197 thread.daemon = True
198 thread.start()
199 if parallel:
200 threads.append(thread)
201 else:
202 thread.join()
203 for thread in threads:
204 thread.join()
205
206
207# TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9
208@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
210 """Makes sure there is no GIL deadlock when running in a thread.
211
212 It runs in a separate process to be able to stop and assert if it deadlocks.
213 """
214 assert _run_in_process(_run_in_threads, test_fn, num_threads=1, parallel=False) == 0
215
216
217# TODO: FIXME on macOS Python 3.9
218@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
220 """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
221
222 It runs in a separate process to be able to stop and assert if it deadlocks.
223 """
224 assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=True) == 0
225
226
227# TODO: FIXME on macOS Python 3.9
228@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
230 """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
231
232 It runs in a separate process to be able to stop and assert if it deadlocks.
233 """
234 assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=False) == 0
235
236
237# TODO: FIXME on macOS Python 3.9
238@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
240 """Makes sure there is no GIL deadlock when using processes.
241
242 This test is for completion, but it was never an issue.
243 """
244 assert _run_in_process(test_fn) == 0
Definition: pytypes.h:1694
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2002
def test_cross_module_gil_inner_pybind11_acquired()
def test_cross_module_gil_released()
def test_cross_module_gil_nested_custom_acquired()
def test_callback_py_obj()
def test_run_in_process_multiple_threads_sequential(test_fn)
def test_callback_pure_virtual_func()
def test_multi_acquire_release_cross_module()
def test_cross_module_gil_inner_custom_released()
def test_all_basic_tests_completeness()
def test_cross_module_gil_inner_pybind11_released()
def test_cross_module_gil_inner_custom_acquired()
def _run_in_process(target, *args, **kwargs)
def test_run_in_process_multiple_threads_parallel(test_fn)
def test_cross_module_gil_nested_pybind11_acquired()
def test_release_acquire()
def test_cross_module_gil_nested_custom_released()
def test_cross_module_gil_nested_pybind11_released()
def test_cross_module_gil_acquired()
def _run_in_threads(test_fn, num_threads, parallel)
def test_callback_std_func()
def test_run_in_process_direct(test_fn)
def test_callback_virtual_func()
def test_run_in_process_one_thread(test_fn)