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


gtsam
Author(s):
autogenerated on Wed Mar 19 2025 03:06:17