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_FatalError("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_FatalError("PyMUTEX_LOCK(" #mut ") failed"); };
63 #define MUTEX_UNLOCK(mut) \
64 if (PyMUTEX_UNLOCK(&(mut))) { \
65 Py_FatalError("PyMUTEX_UNLOCK(" #mut ") failed"); };
66
67 #define COND_INIT(cond) \
68 if (PyCOND_INIT(&(cond))) { \
69 Py_FatalError("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_FatalError("PyCOND_SIGNAL(" #cond ") failed"); };
76 #define COND_WAIT(cond, mut) \
77 if (PyCOND_WAIT(&(cond), &(mut))) { \
78 Py_FatalError("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_FatalError("PyCOND_WAIT(" #cond ") failed"); \
84 if (r) /* 1 == timeout, 2 == impl. can't say, so assume timeout */ \
85 timeout_result = 1; \
86 else \
87 timeout_result = 0; \
88 } \
89
90
91 #define DEFAULT_INTERVAL 5000
92
_gil_initialize(struct _gil_runtime_state * gil)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
gil_created(struct _gil_runtime_state * gil)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
create_gil(struct _gil_runtime_state * gil)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
destroy_gil(struct _gil_runtime_state * gil)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 #ifdef HAVE_FORK
recreate_gil(struct _gil_runtime_state * gil)137 static void recreate_gil(struct _gil_runtime_state *gil)
138 {
139 _Py_ANNOTATE_RWLOCK_DESTROY(&gil->locked);
140 /* XXX should we destroy the old OS resources here? */
141 create_gil(gil);
142 }
143 #endif
144
145 static void
drop_gil(struct _ceval_runtime_state * ceval,struct _ceval_state * ceval2,PyThreadState * tstate)146 drop_gil(struct _ceval_runtime_state *ceval, struct _ceval_state *ceval2,
147 PyThreadState *tstate)
148 {
149 struct _gil_runtime_state *gil = &ceval->gil;
150 if (!_Py_atomic_load_relaxed(&gil->locked)) {
151 Py_FatalError("drop_gil: GIL is not locked");
152 }
153
154 /* tstate is allowed to be NULL (early interpreter init) */
155 if (tstate != NULL) {
156 /* Sub-interpreter support: threads might have been switched
157 under our feet using PyThreadState_Swap(). Fix the GIL last
158 holder variable so that our heuristics work. */
159 _Py_atomic_store_relaxed(&gil->last_holder, (uintptr_t)tstate);
160 }
161
162 MUTEX_LOCK(gil->mutex);
163 _Py_ANNOTATE_RWLOCK_RELEASED(&gil->locked, /*is_write=*/1);
164 _Py_atomic_store_relaxed(&gil->locked, 0);
165 COND_SIGNAL(gil->cond);
166 MUTEX_UNLOCK(gil->mutex);
167
168 #ifdef FORCE_SWITCHING
169 if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request) && tstate != NULL) {
170 MUTEX_LOCK(gil->switch_mutex);
171 /* Not switched yet => wait */
172 if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
173 {
174 assert(is_tstate_valid(tstate));
175 RESET_GIL_DROP_REQUEST(tstate->interp);
176 /* NOTE: if COND_WAIT does not atomically start waiting when
177 releasing the mutex, another thread can run through, take
178 the GIL and drop it again, and reset the condition
179 before we even had a chance to wait for it. */
180 COND_WAIT(gil->switch_cond, gil->switch_mutex);
181 }
182 MUTEX_UNLOCK(gil->switch_mutex);
183 }
184 #endif
185 }
186
187
188 /* Check if a Python thread must exit immediately, rather than taking the GIL
189 if Py_Finalize() has been called.
190
191 When this function is called by a daemon thread after Py_Finalize() has been
192 called, the GIL does no longer exist.
193
194 tstate must be non-NULL. */
195 static inline int
tstate_must_exit(PyThreadState * tstate)196 tstate_must_exit(PyThreadState *tstate)
197 {
198 /* bpo-39877: Access _PyRuntime directly rather than using
199 tstate->interp->runtime to support calls from Python daemon threads.
200 After Py_Finalize() has been called, tstate can be a dangling pointer:
201 point to PyThreadState freed memory. */
202 PyThreadState *finalizing = _PyRuntimeState_GetFinalizing(&_PyRuntime);
203 return (finalizing != NULL && finalizing != tstate);
204 }
205
206
207 /* Take the GIL.
208
209 The function saves errno at entry and restores its value at exit.
210
211 tstate must be non-NULL. */
212 static void
take_gil(PyThreadState * tstate)213 take_gil(PyThreadState *tstate)
214 {
215 int err = errno;
216
217 assert(tstate != NULL);
218
219 if (tstate_must_exit(tstate)) {
220 /* bpo-39877: If Py_Finalize() has been called and tstate is not the
221 thread which called Py_Finalize(), exit immediately the thread.
222
223 This code path can be reached by a daemon thread after Py_Finalize()
224 completes. In this case, tstate is a dangling pointer: points to
225 PyThreadState freed memory. */
226 PyThread_exit_thread();
227 }
228
229 assert(is_tstate_valid(tstate));
230 PyInterpreterState *interp = tstate->interp;
231 struct _ceval_runtime_state *ceval = &interp->runtime->ceval;
232 struct _ceval_state *ceval2 = &interp->ceval;
233 struct _gil_runtime_state *gil = &ceval->gil;
234
235 /* Check that _PyEval_InitThreads() was called to create the lock */
236 assert(gil_created(gil));
237
238 MUTEX_LOCK(gil->mutex);
239
240 if (!_Py_atomic_load_relaxed(&gil->locked)) {
241 goto _ready;
242 }
243
244 int drop_requested = 0;
245 while (_Py_atomic_load_relaxed(&gil->locked)) {
246 unsigned long saved_switchnum = gil->switch_number;
247
248 unsigned long interval = (gil->interval >= 1 ? gil->interval : 1);
249 int timed_out = 0;
250 COND_TIMED_WAIT(gil->cond, gil->mutex, interval, timed_out);
251
252 /* If we timed out and no switch occurred in the meantime, it is time
253 to ask the GIL-holding thread to drop it. */
254 if (timed_out &&
255 _Py_atomic_load_relaxed(&gil->locked) &&
256 gil->switch_number == saved_switchnum)
257 {
258 if (tstate_must_exit(tstate)) {
259 MUTEX_UNLOCK(gil->mutex);
260 // gh-96387: If the loop requested a drop request in a previous
261 // iteration, reset the request. Otherwise, drop_gil() can
262 // block forever waiting for the thread which exited. Drop
263 // requests made by other threads are also reset: these threads
264 // may have to request again a drop request (iterate one more
265 // time).
266 if (drop_requested) {
267 RESET_GIL_DROP_REQUEST(interp);
268 }
269 PyThread_exit_thread();
270 }
271 assert(is_tstate_valid(tstate));
272
273 SET_GIL_DROP_REQUEST(interp);
274 drop_requested = 1;
275 }
276 }
277
278 _ready:
279 #ifdef FORCE_SWITCHING
280 /* This mutex must be taken before modifying gil->last_holder:
281 see drop_gil(). */
282 MUTEX_LOCK(gil->switch_mutex);
283 #endif
284 /* We now hold the GIL */
285 _Py_atomic_store_relaxed(&gil->locked, 1);
286 _Py_ANNOTATE_RWLOCK_ACQUIRED(&gil->locked, /*is_write=*/1);
287
288 if (tstate != (PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) {
289 _Py_atomic_store_relaxed(&gil->last_holder, (uintptr_t)tstate);
290 ++gil->switch_number;
291 }
292
293 #ifdef FORCE_SWITCHING
294 COND_SIGNAL(gil->switch_cond);
295 MUTEX_UNLOCK(gil->switch_mutex);
296 #endif
297
298 if (tstate_must_exit(tstate)) {
299 /* bpo-36475: If Py_Finalize() has been called and tstate is not
300 the thread which called Py_Finalize(), exit immediately the
301 thread.
302
303 This code path can be reached by a daemon thread which was waiting
304 in take_gil() while the main thread called
305 wait_for_thread_shutdown() from Py_Finalize(). */
306 MUTEX_UNLOCK(gil->mutex);
307 drop_gil(ceval, ceval2, tstate);
308 PyThread_exit_thread();
309 }
310 assert(is_tstate_valid(tstate));
311
312 if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) {
313 RESET_GIL_DROP_REQUEST(interp);
314 }
315 else {
316 /* bpo-40010: eval_breaker should be recomputed to be set to 1 if there
317 is a pending signal: signal received by another thread which cannot
318 handle signals.
319
320 Note: RESET_GIL_DROP_REQUEST() calls COMPUTE_EVAL_BREAKER(). */
321 COMPUTE_EVAL_BREAKER(interp, ceval, ceval2);
322 }
323
324 /* Don't access tstate if the thread must exit */
325 if (tstate->async_exc != NULL) {
326 _PyEval_SignalAsyncExc(tstate->interp);
327 }
328
329 MUTEX_UNLOCK(gil->mutex);
330
331 errno = err;
332 }
333
_PyEval_SetSwitchInterval(unsigned long microseconds)334 void _PyEval_SetSwitchInterval(unsigned long microseconds)
335 {
336 struct _gil_runtime_state *gil = &_PyRuntime.ceval.gil;
337 gil->interval = microseconds;
338 }
339
_PyEval_GetSwitchInterval()340 unsigned long _PyEval_GetSwitchInterval()
341 {
342 struct _gil_runtime_state *gil = &_PyRuntime.ceval.gil;
343 return gil->interval;
344 }
345