1# -*- coding: utf-8 -*-
2import multiprocessing
3import threading
4
5from pybind11_tests import gil_scoped as m
6
7
8def _run_in_process(target, *args, **kwargs):
9    """Runs target in process and returns its exitcode after 10s (None if still alive)."""
10    process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
11    process.daemon = True
12    try:
13        process.start()
14        # Do not need to wait much, 10s should be more than enough.
15        process.join(timeout=10)
16        return process.exitcode
17    finally:
18        if process.is_alive():
19            process.terminate()
20
21
22def _python_to_cpp_to_python():
23    """Calls different C++ functions that come back to Python."""
24
25    class ExtendedVirtClass(m.VirtClass):
26        def virtual_func(self):
27            pass
28
29        def pure_virtual_func(self):
30            pass
31
32    extended = ExtendedVirtClass()
33    m.test_callback_py_obj(lambda: None)
34    m.test_callback_std_func(lambda: None)
35    m.test_callback_virtual_func(extended)
36    m.test_callback_pure_virtual_func(extended)
37
38
39def _python_to_cpp_to_python_from_threads(num_threads, parallel=False):
40    """Calls different C++ functions that come back to Python, from Python threads."""
41    threads = []
42    for _ in range(num_threads):
43        thread = threading.Thread(target=_python_to_cpp_to_python)
44        thread.daemon = True
45        thread.start()
46        if parallel:
47            threads.append(thread)
48        else:
49            thread.join()
50    for thread in threads:
51        thread.join()
52
53
54# TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9
55def test_python_to_cpp_to_python_from_thread():
56    """Makes sure there is no GIL deadlock when running in a thread.
57
58    It runs in a separate process to be able to stop and assert if it deadlocks.
59    """
60    assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0
61
62
63# TODO: FIXME on macOS Python 3.9
64def test_python_to_cpp_to_python_from_thread_multiple_parallel():
65    """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
66
67    It runs in a separate process to be able to stop and assert if it deadlocks.
68    """
69    assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0
70
71
72# TODO: FIXME on macOS Python 3.9
73def test_python_to_cpp_to_python_from_thread_multiple_sequential():
74    """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
75
76    It runs in a separate process to be able to stop and assert if it deadlocks.
77    """
78    assert (
79        _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0
80    )
81
82
83# TODO: FIXME on macOS Python 3.9
84def test_python_to_cpp_to_python_from_process():
85    """Makes sure there is no GIL deadlock when using processes.
86
87    This test is for completion, but it was never an issue.
88    """
89    assert _run_in_process(_python_to_cpp_to_python) == 0
90
91
92def test_cross_module_gil():
93    """Makes sure that the GIL can be acquired by another module from a GIL-released state."""
94    m.test_cross_module_gil()  # Should not raise a SIGSEGV
95