Coverage Report

Created: 2022-07-08 09:39

/home/mdboom/Work/builds/cpython/Python/ceval_gil.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Implementation of the Global Interpreter Lock (GIL).
3
 */
4
5
#include <stdlib.h>
6
#include <errno.h>
7
8
#include "pycore_atomic.h"
9
10
11
/*
12
   Notes about the implementation:
13
14
   - The GIL is just a boolean variable (locked) whose access is protected
15
     by a mutex (gil_mutex), and whose changes are signalled by a condition
16
     variable (gil_cond). gil_mutex is taken for short periods of time,
17
     and therefore mostly uncontended.
18
19
   - In the GIL-holding thread, the main loop (PyEval_EvalFrameEx) must be
20
     able to release the GIL on demand by another thread. A volatile boolean
21
     variable (gil_drop_request) is used for that purpose, which is checked
22
     at every turn of the eval loop. That variable is set after a wait of
23
     `interval` microseconds on `gil_cond` has timed out.
24
25
      [Actually, another volatile boolean variable (eval_breaker) is used
26
       which ORs several conditions into one. Volatile booleans are
27
       sufficient as inter-thread signalling means since Python is run
28
       on cache-coherent architectures only.]
29
30
   - A thread wanting to take the GIL will first let pass a given amount of
31
     time (`interval` microseconds) before setting gil_drop_request. This
32
     encourages a defined switching period, but doesn't enforce it since
33
     opcodes can take an arbitrary time to execute.
34
35
     The `interval` value is available for the user to read and modify
36
     using the Python API `sys.{get,set}switchinterval()`.
37
38
   - When a thread releases the GIL and gil_drop_request is set, that thread
39
     ensures that another GIL-awaiting thread gets scheduled.
40
     It does so by waiting on a condition variable (switch_cond) until
41
     the value of last_holder is changed to something else than its
42
     own thread state pointer, indicating that another thread was able to
43
     take the GIL.
44
45
     This is meant to prohibit the latency-adverse behaviour on multi-core
46
     machines where one thread would speculatively release the GIL, but still
47
     run and end up being the first to re-acquire it, making the "timeslices"
48
     much longer than expected.
49
     (Note: this mechanism is enabled with FORCE_SWITCHING above)
50
*/
51
52
#include "condvar.h"
53
54
#define MUTEX_INIT(mut) \
55
    if (PyMUTEX_INIT(&(mut))) { \
56
        
Py_FatalError0
("PyMUTEX_INIT(" #mut ") failed"); };
57
#define MUTEX_FINI(mut) \
58
    if (PyMUTEX_FINI(&(mut))) { \
59
        Py_FatalError("PyMUTEX_FINI(" #mut ") failed"); };
60
#define MUTEX_LOCK(mut) \
61
    if (PyMUTEX_LOCK(&(mut))) { \
62
        
Py_FatalError0
("PyMUTEX_LOCK(" #mut ") failed"); };
63
#define MUTEX_UNLOCK(mut) \
64
    if (PyMUTEX_UNLOCK(&(mut))) { \
65
        
Py_FatalError0
("PyMUTEX_UNLOCK(" #mut ") failed"); };
66
67
#define COND_INIT(cond) \
68
    if (PyCOND_INIT(&(cond))) { \
69
        
Py_FatalError0
("PyCOND_INIT(" #cond ") failed"); };
70
#define COND_FINI(cond) \
71
    if (PyCOND_FINI(&(cond))) { \
72
        Py_FatalError("PyCOND_FINI(" #cond ") failed"); };
73
#define COND_SIGNAL(cond) \
74
    if (PyCOND_SIGNAL(&(cond))) { \
75
        
Py_FatalError0
("PyCOND_SIGNAL(" #cond ") failed"); };
76
#define COND_WAIT(cond, mut) \
77
    if (PyCOND_WAIT(&(cond), &(mut))) { \
78
        
Py_FatalError0
("PyCOND_WAIT(" #cond ") failed"); };
79
#define COND_TIMED_WAIT(cond, mut, microseconds, timeout_result) \
80
    { \
81
        int r = PyCOND_TIMEDWAIT(&(cond), &(mut), (microseconds)); \
82
        if (r < 0) \
83
            
Py_FatalError0
("PyCOND_WAIT(" #cond ") failed"); \
84
        if (r) /* 1 == timeout, 2 == impl. can't say, so assume timeout */ \
85
            
timeout_result = 1123k
; \
86
        else \
87
            
timeout_result = 0171k
; \
88
    } \
89
90
91
#define DEFAULT_INTERVAL 5000
92
93
static void _gil_initialize(struct _gil_runtime_state *gil)
94
{
95
    _Py_atomic_int uninitialized = {-1};
96
    gil->locked = uninitialized;
97
    gil->interval = DEFAULT_INTERVAL;
98
}
99
100
static int gil_created(struct _gil_runtime_state *gil)
101
{
102
    return (_Py_atomic_load_explicit(&gil->locked, _Py_memory_order_acquire) >= 0);
103
}
104
105
static void create_gil(struct _gil_runtime_state *gil)
106
{
107
    MUTEX_INIT(gil->mutex);
108
#ifdef FORCE_SWITCHING
109
    MUTEX_INIT(gil->switch_mutex);
110
#endif
111
    COND_INIT(gil->cond);
112
#ifdef FORCE_SWITCHING
113
    COND_INIT(gil->switch_cond);
114
#endif
115
    _Py_atomic_store_relaxed(&gil->last_holder, 0);
116
    _Py_ANNOTATE_RWLOCK_CREATE(&gil->locked);
117
    _Py_atomic_store_explicit(&gil->locked, 0, _Py_memory_order_release);
118
}
119
120
static void destroy_gil(struct _gil_runtime_state *gil)
121
{
122
    /* some pthread-like implementations tie the mutex to the cond
123
     * and must have the cond destroyed first.
124
     */
125
    COND_FINI(gil->cond);
126
    MUTEX_FINI(gil->mutex);
127
#ifdef FORCE_SWITCHING
128
    COND_FINI(gil->switch_cond);
129
    MUTEX_FINI(gil->switch_mutex);
130
#endif
131
    _Py_atomic_store_explicit(&gil->locked, -1,
132
                              _Py_memory_order_release);
133
    _Py_ANNOTATE_RWLOCK_DESTROY(&gil->locked);
134
}
135
136
static void recreate_gil(struct _gil_runtime_state *gil)
137
{
138
    _Py_ANNOTATE_RWLOCK_DESTROY(&gil->locked);
139
    /* XXX should we destroy the old OS resources here? */
140
    create_gil(gil);
141
}
142
143
static void
144
drop_gil(struct _ceval_runtime_state *ceval, struct _ceval_state *ceval2,
145
         PyThreadState *tstate)
146
{
147
    struct _gil_runtime_state *gil = &ceval->gil;
148
    if (!_Py_atomic_load_relaxed(&gil->locked)) {
  Branch (148:9): [True: 0, False: 7.77M]
149
        Py_FatalError("drop_gil: GIL is not locked");
150
    }
151
152
    /* tstate is allowed to be NULL (early interpreter init) */
153
    if (tstate != NULL) {
  Branch (153:9): [True: 7.77M, False: 0]
154
        /* Sub-interpreter support: threads might have been switched
155
           under our feet using PyThreadState_Swap(). Fix the GIL last
156
           holder variable so that our heuristics work. */
157
        _Py_atomic_store_relaxed(&gil->last_holder, (uintptr_t)tstate);
158
    }
159
160
    MUTEX_LOCK(gil->mutex);
161
    _Py_ANNOTATE_RWLOCK_RELEASED(&gil->locked, /*is_write=*/1);
162
    _Py_atomic_store_relaxed(&gil->locked, 0);
163
    COND_SIGNAL(gil->cond);
164
    MUTEX_UNLOCK(gil->mutex);
165
166
#ifdef FORCE_SWITCHING
167
    if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request) && 
tstate != NULL39.5k
) {
  Branch (167:63): [True: 39.5k, False: 0]
168
        MUTEX_LOCK(gil->switch_mutex);
169
        /* Not switched yet => wait */
170
        if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
  Branch (170:13): [True: 22.3k, False: 17.2k]
171
        {
172
            assert(is_tstate_valid(tstate));
173
            RESET_GIL_DROP_REQUEST(tstate->interp);
174
            /* NOTE: if COND_WAIT does not atomically start waiting when
175
               releasing the mutex, another thread can run through, take
176
               the GIL and drop it again, and reset the condition
177
               before we even had a chance to wait for it. */
178
            COND_WAIT(gil->switch_cond, gil->switch_mutex);
179
        }
180
        
MUTEX_UNLOCK39.5k
(gil->switch_mutex);
181
    }
182
#endif
183
}
184
185
186
/* Check if a Python thread must exit immediately, rather than taking the GIL
187
   if Py_Finalize() has been called.
188
189
   When this function is called by a daemon thread after Py_Finalize() has been
190
   called, the GIL does no longer exist.
191
192
   tstate must be non-NULL. */
193
static inline int
194
tstate_must_exit(PyThreadState *tstate)
195
{
196
    /* bpo-39877: Access _PyRuntime directly rather than using
197
       tstate->interp->runtime to support calls from Python daemon threads.
198
       After Py_Finalize() has been called, tstate can be a dangling pointer:
199
       point to PyThreadState freed memory. */
200
    PyThreadState *finalizing = _PyRuntimeState_GetFinalizing(&_PyRuntime);
201
    return (finalizing != NULL && 
finalizing != tstate1.10k
);
  Branch (201:13): [True: 1.10k, False: 15.6M]
  Branch (201:35): [True: 0, False: 1.10k]
202
}
203
204
205
/* Take the GIL.
206
207
   The function saves errno at entry and restores its value at exit.
208
209
   tstate must be non-NULL. */
210
static void
211
take_gil(PyThreadState *tstate)
212
{
213
    int err = errno;
214
215
    assert(tstate != NULL);
216
217
    if (tstate_must_exit(tstate)) {
  Branch (217:9): [True: 0, False: 7.77M]
218
        /* bpo-39877: If Py_Finalize() has been called and tstate is not the
219
           thread which called Py_Finalize(), exit immediately the thread.
220
221
           This code path can be reached by a daemon thread after Py_Finalize()
222
           completes. In this case, tstate is a dangling pointer: points to
223
           PyThreadState freed memory. */
224
        PyThread_exit_thread();
225
    }
226
227
    assert(is_tstate_valid(tstate));
228
    PyInterpreterState *interp = tstate->interp;
229
    struct _ceval_runtime_state *ceval = &interp->runtime->ceval;
230
    struct _ceval_state *ceval2 = &interp->ceval;
231
    struct _gil_runtime_state *gil = &ceval->gil;
232
233
    /* Check that _PyEval_InitThreads() was called to create the lock */
234
    assert(gil_created(gil));
235
236
    MUTEX_LOCK(gil->mutex);
237
238
    if (!_Py_atomic_load_relaxed(&gil->locked)) {
  Branch (238:9): [True: 7.64M, False: 122k]
239
        goto _ready;
240
    }
241
242
    
while (_Py_atomic_load_relaxed(&gil->locked))122k
{
243
        unsigned long saved_switchnum = gil->switch_number;
244
245
        unsigned long interval = (gil->interval >= 1 ? gil->interval : 
10
);
  Branch (245:35): [True: 294k, False: 0]
246
        int timed_out = 0;
247
        COND_TIMED_WAIT(gil->cond, gil->mutex, interval, timed_out);
248
249
        /* If we timed out and no switch occurred in the meantime, it is time
250
           to ask the GIL-holding thread to drop it. */
251
        if (timed_out &&
  Branch (251:13): [True: 123k, False: 171k]
252
            _Py_atomic_load_relaxed(&gil->locked) &&
253
            
gil->switch_number == saved_switchnum123k
)
  Branch (253:13): [True: 120k, False: 2.06k]
254
        {
255
            if (tstate_must_exit(tstate)) {
  Branch (255:17): [True: 0, False: 120k]
256
                MUTEX_UNLOCK(gil->mutex);
257
                PyThread_exit_thread();
258
            }
259
            assert(is_tstate_valid(tstate));
260
261
            SET_GIL_DROP_REQUEST(interp);
262
        }
263
    }
264
265
_ready:
266
#ifdef FORCE_SWITCHING
267
    /* This mutex must be taken before modifying gil->last_holder:
268
       see drop_gil(). */
269
    MUTEX_LOCK(gil->switch_mutex);
270
#endif
271
    /* We now hold the GIL */
272
    _Py_atomic_store_relaxed(&gil->locked, 1);
273
    _Py_ANNOTATE_RWLOCK_ACQUIRED(&gil->locked, /*is_write=*/1);
274
275
    if (tstate != (PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) {
  Branch (275:9): [True: 185k, False: 7.58M]
276
        _Py_atomic_store_relaxed(&gil->last_holder, (uintptr_t)tstate);
277
        ++gil->switch_number;
278
    }
279
280
#ifdef FORCE_SWITCHING
281
    COND_SIGNAL(gil->switch_cond);
282
    MUTEX_UNLOCK(gil->switch_mutex);
283
#endif
284
285
    if (tstate_must_exit(tstate)) {
  Branch (285:9): [True: 0, False: 7.77M]
286
        /* bpo-36475: If Py_Finalize() has been called and tstate is not
287
           the thread which called Py_Finalize(), exit immediately the
288
           thread.
289
290
           This code path can be reached by a daemon thread which was waiting
291
           in take_gil() while the main thread called
292
           wait_for_thread_shutdown() from Py_Finalize(). */
293
        MUTEX_UNLOCK(gil->mutex);
294
        drop_gil(ceval, ceval2, tstate);
295
        PyThread_exit_thread();
296
    }
297
    assert(is_tstate_valid(tstate));
298
299
    if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) {
300
        RESET_GIL_DROP_REQUEST(interp);
301
    }
302
    else {
303
        /* bpo-40010: eval_breaker should be recomputed to be set to 1 if there
304
           is a pending signal: signal received by another thread which cannot
305
           handle signals.
306
307
           Note: RESET_GIL_DROP_REQUEST() calls COMPUTE_EVAL_BREAKER(). */
308
        COMPUTE_EVAL_BREAKER(interp, ceval, ceval2);
309
    }
310
311
    /* Don't access tstate if the thread must exit */
312
    if (tstate->async_exc != NULL) {
  Branch (312:9): [True: 1, False: 7.77M]
313
        _PyEval_SignalAsyncExc(tstate->interp);
314
    }
315
316
    MUTEX_UNLOCK(gil->mutex);
317
318
    errno = err;
319
}
320
321
void _PyEval_SetSwitchInterval(unsigned long microseconds)
322
{
323
    struct _gil_runtime_state *gil = &_PyRuntime.ceval.gil;
324
    gil->interval = microseconds;
325
}
326
327
unsigned long _PyEval_GetSwitchInterval()
328
{
329
    struct _gil_runtime_state *gil = &_PyRuntime.ceval.gil;
330
    return gil->interval;
331
}