test_gil_scoped.py
Go to the documentation of this file.
1 import multiprocessing
2 import sys
3 import threading
4 import time
5 
6 import pytest
7 
8 import env
9 from pybind11_tests import gil_scoped as m
10 
11 
12 class ExtendedVirtClass(m.VirtClass):
13  def virtual_func(self):
14  pass
15 
16  def pure_virtual_func(self):
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.
111 VARS_BEFORE_ALL_BASIC_TESTS = dict(vars()) # Make a copy of the dict (critical).
112 ALL_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 
147 ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_deadlock,)
148 
149 
150 def _run_in_process(target, *args, **kwargs):
151  test_fn = target if len(args) == 0 else args[0]
152  # Do not need to wait much, 10s should be more than enough.
153  timeout = 0.1 if test_fn is _intentional_deadlock else 10
154  process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
155  process.daemon = True
156  try:
157  t_start = time.time()
158  process.start()
159  if timeout >= 100: # For debugging.
160  print(
161  "\nprocess.pid STARTED", process.pid, (sys.argv, target, args, kwargs)
162  )
163  print(f"COPY-PASTE-THIS: gdb {sys.argv[0]} -p {process.pid}", flush=True)
164  process.join(timeout=timeout)
165  if timeout >= 100:
166  print("\nprocess.pid JOINED", process.pid, flush=True)
167  t_delta = time.time() - t_start
168  if process.exitcode == 66 and m.defined_THREAD_SANITIZER: # Issue #2754
169  # WOULD-BE-NICE-TO-HAVE: Check that the message below is actually in the output.
170  # Maybe this could work:
171  # https://gist.github.com/alexeygrigorev/01ce847f2e721b513b42ea4a6c96905e
172  pytest.skip(
173  "ThreadSanitizer: starting new threads after multi-threaded fork is not supported."
174  )
175  elif test_fn is _intentional_deadlock:
176  assert process.exitcode is None
177  return 0
178 
179  if process.exitcode is None:
180  assert t_delta > 0.9 * timeout
181  msg = "DEADLOCK, most likely, exactly what this test is meant to detect."
182  if env.PYPY and env.WIN:
183  pytest.skip(msg)
184  raise RuntimeError(msg)
185  return process.exitcode
186  finally:
187  if process.is_alive():
188  process.terminate()
189 
190 
191 def _run_in_threads(test_fn, num_threads, parallel):
192  threads = []
193  for _ in range(num_threads):
194  thread = threading.Thread(target=test_fn)
195  thread.daemon = True
196  thread.start()
197  if parallel:
198  threads.append(thread)
199  else:
200  thread.join()
201  for thread in threads:
202  thread.join()
203 
204 
205 # TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9
206 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
208  """Makes sure there is no GIL deadlock when running in a thread.
209 
210  It runs in a separate process to be able to stop and assert if it deadlocks.
211  """
212  assert _run_in_process(_run_in_threads, test_fn, num_threads=1, parallel=False) == 0
213 
214 
215 # TODO: FIXME on macOS Python 3.9
216 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
218  """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
219 
220  It runs in a separate process to be able to stop and assert if it deadlocks.
221  """
222  assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=True) == 0
223 
224 
225 # TODO: FIXME on macOS Python 3.9
226 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
228  """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
229 
230  It runs in a separate process to be able to stop and assert if it deadlocks.
231  """
232  assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=False) == 0
233 
234 
235 # TODO: FIXME on macOS Python 3.9
236 @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
238  """Makes sure there is no GIL deadlock when using processes.
239 
240  This test is for completion, but it was never an issue.
241  """
242  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:38
test_gil_scoped.test_cross_module_gil_inner_custom_released
def test_cross_module_gil_inner_custom_released()
Definition: test_gil_scoped.py:48
test_gil_scoped.test_cross_module_gil_nested_custom_released
def test_cross_module_gil_nested_custom_released()
Definition: test_gil_scoped.py:72
test_gil_scoped.test_multi_acquire_release_cross_module
def test_multi_acquire_release_cross_module()
Definition: test_gil_scoped.py:104
test_gil_scoped.ExtendedVirtClass.virtual_func
def virtual_func(self)
Definition: test_gil_scoped.py:13
test_gil_scoped.test_release_acquire
def test_release_acquire()
Definition: test_gil_scoped.py:96
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:217
test_gil_scoped.test_run_in_process_direct
def test_run_in_process_direct(test_fn)
Definition: test_gil_scoped.py:237
test_gil_scoped.test_cross_module_gil_acquired
def test_cross_module_gil_acquired()
Definition: test_gil_scoped.py:43
test_gil_scoped.test_run_in_process_one_thread
def test_run_in_process_one_thread(test_fn)
Definition: test_gil_scoped.py:207
gtsam::range
Double_ range(const Point2_ &p, const Point2_ &q)
Definition: slam/expressions.h:30
dict
Definition: pytypes.h:2065
test_gil_scoped.ExtendedVirtClass.pure_virtual_func
def pure_virtual_func(self)
Definition: test_gil_scoped.py:16
test_gil_scoped.test_cross_module_gil_nested_pybind11_acquired
def test_cross_module_gil_nested_pybind11_acquired()
Definition: test_gil_scoped.py:90
test_gil_scoped.test_cross_module_gil_nested_pybind11_released
def test_cross_module_gil_nested_pybind11_released()
Definition: test_gil_scoped.py:84
test_gil_scoped.test_callback_std_func
def test_callback_std_func()
Definition: test_gil_scoped.py:24
test_gil_scoped.test_callback_py_obj
def test_callback_py_obj()
Definition: test_gil_scoped.py:20
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:227
test_gil_scoped.ExtendedVirtClass
Definition: test_gil_scoped.py:12
test_gil_scoped.test_cross_module_gil_inner_custom_acquired
def test_cross_module_gil_inner_custom_acquired()
Definition: test_gil_scoped.py:54
test_gil_scoped.test_callback_pure_virtual_func
def test_callback_pure_virtual_func()
Definition: test_gil_scoped.py:33
test_gil_scoped.test_cross_module_gil_inner_pybind11_acquired
def test_cross_module_gil_inner_pybind11_acquired()
Definition: test_gil_scoped.py:66
test_gil_scoped.test_nested_acquire
def test_nested_acquire()
Definition: test_gil_scoped.py:100
test_gil_scoped.test_cross_module_gil_inner_pybind11_released
def test_cross_module_gil_inner_pybind11_released()
Definition: test_gil_scoped.py:60
test_gil_scoped.test_all_basic_tests_completeness
def test_all_basic_tests_completeness()
Definition: test_gil_scoped.py:133
test_gil_scoped._run_in_threads
def _run_in_threads(test_fn, num_threads, parallel)
Definition: test_gil_scoped.py:191
len
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2399
test_gil_scoped.test_callback_virtual_func
def test_callback_virtual_func()
Definition: test_gil_scoped.py:28
test_gil_scoped._intentional_deadlock
def _intentional_deadlock()
Definition: test_gil_scoped.py:143
test_gil_scoped._run_in_process
def _run_in_process(target, *args, **kwargs)
Definition: test_gil_scoped.py:150
test_gil_scoped.test_cross_module_gil_nested_custom_acquired
def test_cross_module_gil_nested_custom_acquired()
Definition: test_gil_scoped.py:78


gtsam
Author(s):
autogenerated on Tue Jun 25 2024 03:05:28