94 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Python
		
	
	
		
		
			
		
	
	
			94 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Python
		
	
	
| 
								 | 
							
								import multiprocessing
							 | 
						||
| 
								 | 
							
								import threading
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from pybind11_tests import gil_scoped as m
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _run_in_process(target, *args, **kwargs):
							 | 
						||
| 
								 | 
							
								    """Runs target in process and returns its exitcode after 10s (None if still alive)."""
							 | 
						||
| 
								 | 
							
								    process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
							 | 
						||
| 
								 | 
							
								    process.daemon = True
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        process.start()
							 | 
						||
| 
								 | 
							
								        # Do not need to wait much, 10s should be more than enough.
							 | 
						||
| 
								 | 
							
								        process.join(timeout=10)
							 | 
						||
| 
								 | 
							
								        return process.exitcode
							 | 
						||
| 
								 | 
							
								    finally:
							 | 
						||
| 
								 | 
							
								        if process.is_alive():
							 | 
						||
| 
								 | 
							
								            process.terminate()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _python_to_cpp_to_python():
							 | 
						||
| 
								 | 
							
								    """Calls different C++ functions that come back to Python."""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    class ExtendedVirtClass(m.VirtClass):
							 | 
						||
| 
								 | 
							
								        def virtual_func(self):
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def pure_virtual_func(self):
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    extended = ExtendedVirtClass()
							 | 
						||
| 
								 | 
							
								    m.test_callback_py_obj(lambda: None)
							 | 
						||
| 
								 | 
							
								    m.test_callback_std_func(lambda: None)
							 | 
						||
| 
								 | 
							
								    m.test_callback_virtual_func(extended)
							 | 
						||
| 
								 | 
							
								    m.test_callback_pure_virtual_func(extended)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _python_to_cpp_to_python_from_threads(num_threads, parallel=False):
							 | 
						||
| 
								 | 
							
								    """Calls different C++ functions that come back to Python, from Python threads."""
							 | 
						||
| 
								 | 
							
								    threads = []
							 | 
						||
| 
								 | 
							
								    for _ in range(num_threads):
							 | 
						||
| 
								 | 
							
								        thread = threading.Thread(target=_python_to_cpp_to_python)
							 | 
						||
| 
								 | 
							
								        thread.daemon = True
							 | 
						||
| 
								 | 
							
								        thread.start()
							 | 
						||
| 
								 | 
							
								        if parallel:
							 | 
						||
| 
								 | 
							
								            threads.append(thread)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            thread.join()
							 | 
						||
| 
								 | 
							
								    for thread in threads:
							 | 
						||
| 
								 | 
							
								        thread.join()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9
							 | 
						||
| 
								 | 
							
								def test_python_to_cpp_to_python_from_thread():
							 | 
						||
| 
								 | 
							
								    """Makes sure there is no GIL deadlock when running in a thread.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It runs in a separate process to be able to stop and assert if it deadlocks.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: FIXME on macOS Python 3.9
							 | 
						||
| 
								 | 
							
								def test_python_to_cpp_to_python_from_thread_multiple_parallel():
							 | 
						||
| 
								 | 
							
								    """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It runs in a separate process to be able to stop and assert if it deadlocks.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: FIXME on macOS Python 3.9
							 | 
						||
| 
								 | 
							
								def test_python_to_cpp_to_python_from_thread_multiple_sequential():
							 | 
						||
| 
								 | 
							
								    """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It runs in a separate process to be able to stop and assert if it deadlocks.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert (
							 | 
						||
| 
								 | 
							
								        _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# TODO: FIXME on macOS Python 3.9
							 | 
						||
| 
								 | 
							
								def test_python_to_cpp_to_python_from_process():
							 | 
						||
| 
								 | 
							
								    """Makes sure there is no GIL deadlock when using processes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This test is for completion, but it was never an issue.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert _run_in_process(_python_to_cpp_to_python) == 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def test_cross_module_gil():
							 | 
						||
| 
								 | 
							
								    """Makes sure that the GIL can be acquired by another module from a GIL-released state."""
							 | 
						||
| 
								 | 
							
								    m.test_cross_module_gil()  # Should not raise a SIGSEGV
							 |