File: gil_in_var_initialization_tests.pyx

package info (click to toggle)
cython 3.0.11%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 19,092 kB
  • sloc: python: 83,539; ansic: 18,831; cpp: 1,402; xml: 1,031; javascript: 511; makefile: 403; sh: 204; sed: 11
file content (149 lines) | stat: -rw-r--r-- 4,966 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# mode: run
# tag: cpp, cpp11

#cython: test_fail_if_c_code_has = __Pyx_RefNannySetupContext\("call_a_method_with_an_error_return"
##cython: test_fail_if_c_code_has = /int __pyx_f_31gil_in_var_initialization_tests_1C_call_a_method_with_an_error_return\(.+{/:/__pyx_vtab/PyGILState_Ensure
#cython: test_fail_if_c_code_has = __Pyx_RefNannySetupContext\("call_me"
##cython: test_fail_if_c_code_has = /void __pyx_f_31gil_in_var_initialization_tests_1D_call_me\(.+{/:/get_left_edge\(/PyGILState_Ensure

# patterns above look for unwanted __Pyx_RefNannySetupContext and PyGILState_Ensure calls within
# the introductory part of the two functions (defined by looking at the function name, looking for
# a { to know we're at the function definition, and looking for a distinctive early line in the
# function.
# The second ones are disabled because they are tripped up by the annotated HTML output, where
# the code is jumbled up so they get caught by some GIL use in some unrelated error handling.

from libcpp cimport bool

# What we're testing in this file is that certain bits of code don't acquire the GIL
# at any point during the function.
#
# To do this we create a C++ thread that runs with the GIL permanently held, and
# which is waiting for a condition variable to be set. We run the relevant Cython
# function and then set the condition variable.
#
# If the Cython function requires the GIL it will be blocked by the C++ thread and
# so be unable to set the condition variable.
#
# This isn't specifically a C++ test, but the C++ standard library concurrency tools
# are a convenient way of getting condition variables and mutexes.

cdef extern from *:
    """
    #include <condition_variable>
    #include <mutex>
    #include <chrono>
    #include <future>

    static bool waiting = false;
    static bool triggered = false;
    static std::condition_variable condition_variable;
    static std::mutex mutex;

    void reset_cpp_state() {
        std::unique_lock<std::mutex> lock(mutex);
        waiting = false;
        triggered = false;
    }

    bool block_and_wait_for_trigger() {
        std::unique_lock<std::mutex> lock(mutex);
        waiting = true;
        triggered = false;
        condition_variable.notify_all();
        // Note - I'm using a 5 second timeout here to avoid the test
        // being "deadlock forever". This could occasionally give false
        // failures if things are running really slowly
        return condition_variable.wait_for(
            lock,
            std::chrono::seconds(5),
            []() {
                return triggered;
            });
    }

    void wait_for_waiting() {
        std::unique_lock<std::mutex> lock(mutex);
        condition_variable.wait(
            lock,
            []() {
                return waiting;
            });
    }

    void set_triggered() {
        std::unique_lock<std::mutex> lock(mutex);
        triggered = true;
        condition_variable.notify_all();
    }

    using bool_future = std::future<bool>;

    std::future<bool> run_block_and_wait_with_gil() {
        reset_cpp_state();
        return std::async(std::launch::async,
            [](){
                PyGILState_STATE gstate;
                gstate = PyGILState_Ensure();

                auto result = block_and_wait_for_trigger();

                PyGILState_Release(gstate);

                return result;
            });
    }
    """
    void wait_for_waiting() nogil
    void set_triggered() nogil
    cppclass bool_future:
        bool get() nogil
    bool_future run_block_and_wait_with_gil() nogil

cdef class C:
    cdef int some_c_method(self) except -1 nogil:
        return 0

    cdef int call_a_method_with_an_error_return(self) except -1 nogil:
        return self.some_c_method()

def test_method_with_error_return():
    """
    >>> test_method_with_error_return()
    """
    future = run_block_and_wait_with_gil()
    c = C()
    with nogil:
        wait_for_waiting()  # make sure the C++ thread has started and is holding the GIL
        c.call_a_method_with_an_error_return()
        set_triggered()
    assert future.get()


cdef inline float[:] _get_left_edge(float[::1] arr) nogil:
    return arr[:3]

cdef class D:
    cdef float _a
    def __cinit__(self, float a):
        self._a = a

    cdef void call_me(self, float[::1] my_arr) noexcept nogil:
        cdef Py_ssize_t idx
        cdef float[:] my_arr2 = _get_left_edge(my_arr)
        for idx in range(my_arr2.shape[0]):
            my_arr2[idx] = self._a

def test_method_with_memoryview_handling():
    """
    >>> test_method_with_memoryview_handling()
    """
    cdef float[10] static_arr
    cdef float[::1] view_of_static_arr = <float[:10:1]>static_arr
    future = run_block_and_wait_with_gil()
    d = D(5.)
    with nogil:
        wait_for_waiting()  # make sure the C++ thread has started and is holding the GIL
        d.call_me(view_of_static_arr)
        set_triggered()
    assert future.get()