summaryrefslogtreecommitdiffstats
path: root/contrib/tools/cython/Cython/Includes/libc/threads.pxd
blob: 6caa1f8e65410407c6718229af9a174a1fb20142 (plain) (blame)
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# cython: preliminary_late_includes_cy28=True

from libc.time cimport timespec

cdef extern from *:
    """
    #include <string.h>
    #include <threads.h>
    static void __Pyx_once_flag_init(once_flag* flag) {
        once_flag other = ONCE_FLAG_INIT;
        memcpy(flag, &other, sizeof(once_flag));
    }
    """
    void once_flag_init "__Pyx_once_flag_init"(once_flag*)

cdef extern from "<threads.h>" nogil:
    ctypedef void (*_once_func_type)() noexcept nogil

    # Threads
    ctypedef struct thrd_t:
        pass
    # Declare as noexcept because you'll regret trying to throw a Python exception from it
    # and nogil because it's a new thread, so you definitely don't have a Python threadstate yet.
    ctypedef int (*thrd_start_t)(void*) noexcept nogil

    int thrd_create(thrd_t*, thrd_start_t, void*)
    int thrd_equal(thrd_t lhs, thrd_t rhs)
    thrd_t thrd_current()
    int thrd_sleep(const timespec* duration, timespec* remaining)
    void thrd_yield()
    void thrd_exit(int res)
    void thrd_detach(thrd_t thr)
    # Be very wary of deadlocks if calling thrd_join with the GIL.
    int thrd_join(thrd_t, int *res)
    enum:
        thrd_success
        thrd_nomem
        thrd_timedout
        thrd_busy
        thrd_error

    # Mutexes.
    # We strongly recommend not calling mtx_lock with the GIL held and being
    # very wary of calling mtx_timed_lock with the GIL (because it will block the GIL until the timeout).
    ctypedef struct mtx_t:
        pass
    int mtx_init(mtx_t* mtx, int type)
    int mtx_lock(mtx_t* mtx)  # see py_safe_mutex_lock for GIL-safe version
    int mtx_timed_lock(mtx_t* mtx, const timespec* timepoint)
    int mtx_trylock(mtx_t* mtx)
    int mtx_unlock(mtx_t* mtx)
    void mtx_destroy(mtx_t* mtx)
    enum:
        mtx_plain
        mtx_recursive
        mtx_timed

    # call once. Potentially this may be hard to use with Cython's code generation
    # (depending on the exact definition of "ONCE_FLAG_INIT").
    # Use the helper function "once_flag_init" to do it non-statically.
    ctypedef struct once_flag:
        pass
    once_flag ONCE_FLAG_INIT

    # We strongly recommend not calling this with the GIL held.
    # We define some GIL-friendly wrappers for C++ call-once which you can use
    # in these circumstances.
    # py_safe_call_once in this file avoids blocking, but the function
    # must still be nogil.
    void call_once(once_flag* flag, _once_func_type func) noexcept

    # condition variables
    ctypedef struct cnd_t:
        pass
    int cnd_init(cnd_t*)
    int cnd_signal(cnd_t*)
    int cnd_broadcast(cnd_t*)
    # Be very wary of calling wait/timedwait with the GIL held. Two py_safe_ versions exist.
    int cnd_wait(cnd_t*, mtx_t*)
    int cnd_timedwait(cnd_t*, mtx_t*, const timespec*)
    void cnd_destroy(cnd_t*)

    # Thread-local storage
    ctypedef struct tss_t:
        pass
    enum:
        TSS_DTOR_ITERATIONS
    ctypedef void (*tss_dtor_t)(void*) noexcept nogil
    int tss_create(tss_t* key, tss_dtor_t destructor)
    void *tss_get(tss_t key)
    int tss_set(tss_t key, void* val)
    void tss_delete(tss_t key)


cdef inline void _dummy_force_utility_code_inclusion() nogil:
    with nogil:
        pass

cdef extern from *:
    ctypedef struct py_safe_once_flag "once_flag":
        pass
    py_safe_once_flag PY_SAFE_ONCE_FLAG_INIT "ONCE_FLAG_INIT"

cdef extern from *:
    """
    static PyGILState_STATE __pyx_libc_threads_limited_api_ensure_gil() {
        #if CYTHON_COMPILING_IN_LIMITED_API
        if ((__PYX_LIMITED_VERSION_HEX < 0x030d0000) && __Pyx_get_runtime_version() < 0x030d0000) {
            return PyGILState_Ensure();
        }
        #endif
        return PyGILState_LOCKED;  // Unused
    }

    #if CYTHON_COMPILING_IN_LIMITED_API && __PYX_LIMITED_VERSION_HEX < 0x030d0000
    static void __pyx_libc_threads_limited_api_release_gil(PyGILState_STATE gil_state) {
        if (__Pyx_get_runtime_version() < 0x030d0000)
            PyGILState_Release(gil_state);
    }
    #else
    #define __pyx_libc_threads_limited_api_release_gil(ignore) (void)ignore
    #endif

    static int __pyx_libc_threads_has_gil() {
        #if CYTHON_COMPILING_IN_LIMITED_API
            if ((__PYX_LIMITED_VERSION_HEX >= 0x030d0000) || __Pyx_get_runtime_version() >= 0x030d0000) {
                // In 3.13+ we can temporarily give up the GIL to find out what the thread state was.
                PyThreadState *ts = PyThreadState_Swap(NULL);
                if (ts) {
                    PyThreadState_Swap(ts);
                    return 1;
                }
                return 0;
            }
            /* There is no way to know if we have the GIL. Therefore the only
             * thing we can safely do is make absolutely sure that we have it
             * (in __pyx_libc_threads_limited_api_ensure_gil).
             */
            return 1;
        #elif PY_VERSION_HEX >= 0x030d0000
            return PyThreadState_GetUnchecked() != NULL;
        #elif PY_VERSION_HEX >= 0x030C0000
            return _PyThreadState_UncheckedGet() != NULL;
        #else
            return PyGILState_Check();
        #endif
    }

    static int __pyx_py_safe_mtx_lock_release_lock_reacquire(mtx_t* mutex) {
        // Release the GIL, acquire the lock, then reacquire the GIL.
        // This is safe provided the user never holds the GIL while trying
        // to reacquire the lock (i.e. it's safe provided they always use
        // the py-safe wrappers).
        int result;
        Py_BEGIN_ALLOW_THREADS
        result = mtx_lock(mutex);
        Py_END_ALLOW_THREADS
        return result;
    }

    static int __pyx_py_safe_mtx_lock_impl(mtx_t* mutex) {
        int lock_result = mtx_trylock(mutex);
        if (lock_result == thrd_busy) {
            return __pyx_py_safe_mtx_lock_release_lock_reacquire(mutex);
        }
        return lock_result;
    }

    CYTHON_UNUSED static int __pyx_py_safe_mtx_lock(mtx_t* mutex) {
        PyGILState_STATE gil_state = __pyx_libc_threads_limited_api_ensure_gil();
        if (!__pyx_libc_threads_has_gil())
            return mtx_lock(mutex); /* No GIL, no problem */
        int result = __pyx_py_safe_mtx_lock_impl(mutex);
        __pyx_libc_threads_limited_api_release_gil(gil_state);
        return result;
    }

    CYTHON_UNUSED static int __pyx_py_safe_cnd_wait( cnd_t* cond, mtx_t* mutex) {
        __Pyx_UnknownThreadState thread_state = __Pyx_SaveUnknownThread();
        int result = cnd_wait(cond, mutex);
        if (__Pyx_UnknownThreadStateMayHaveHadGil(thread_state)) {
            int unlock_result, relock_result;
            unlock_result = mtx_unlock(mutex);
            __Pyx_RestoreUnknownThread(thread_state);
            PyGILState_STATE gil_state = __pyx_libc_threads_limited_api_ensure_gil();
            relock_result = __pyx_py_safe_mtx_lock_impl(mutex);
            __pyx_libc_threads_limited_api_release_gil(gil_state);
            return relock_result != thrd_success ? relock_result :
                unlock_result != thrd_success ? unlock_result :
                result;
        } else {
            __Pyx_RestoreUnknownThread(thread_state);
            return result;
        }
    }

    CYTHON_UNUSED static int __pyx_py_safe_cnd_timedwait(cnd_t* cond, mtx_t* mutex, const struct timespec* time_point) {
        __Pyx_UnknownThreadState thread_state = __Pyx_SaveUnknownThread();
        int result = cnd_timedwait(cond, mutex, time_point);
        if (__Pyx_UnknownThreadStateMayHaveHadGil(thread_state)) {
            int unlock_result, relock_result;
            unlock_result = mtx_unlock(mutex);
            __Pyx_RestoreUnknownThread(thread_state);
            PyGILState_STATE gil_state = __pyx_libc_threads_limited_api_ensure_gil();
            relock_result = __pyx_py_safe_mtx_lock_impl(mutex);
            __pyx_libc_threads_limited_api_release_gil(gil_state);
            return relock_result != thrd_success ? relock_result :
                unlock_result != thrd_success ? unlock_result :
                result;
        } else {
            __Pyx_RestoreUnknownThread(thread_state);
            return result;
        }
    }

    CYTHON_UNUSED static void __pyx_libc_threads_py_safe_call_once(once_flag* flag, void (*func)(void)) {
        __Pyx_UnknownThreadState thread_state = __Pyx_SaveUnknownThread();
        call_once(flag, func);
        __Pyx_RestoreUnknownThread(thread_state);
    }
    """
    # Acquire the lock without deadlocking on the GIL.
    # At the end, the GIL will be restored to its original state.
    int py_safe_mtx_lock "__pyx_py_safe_mtx_lock"(mtx_t* mutex) nogil

    # Wait on the condition variable without deadlocking on the GIL.
    # At the end, the GIL will be restored to its original state.
    int py_safe_cnd_wait "__pyx_py_safe_cnd_wait"(cnd_t*, mtx_t*) nogil
    int py_safe_cnd_timedwait "__pyx_py_safe_cnd_timedwait"(cnd_t*, mtx_t*, const timespec*) nogil

    # call_once making sure not to deadlock on the GIL.
    # The callable must still be nogil though.
    void py_safe_once_flag_init "__Pyx_once_flag_init"(py_safe_once_flag*)
    int py_safe_call_once "__pyx_libc_threads_py_safe_call_once"(py_safe_once_flag* flag, _once_func_type func) nogil