Skip to content

Commit e324003

Browse files
committed
Enable the GIL while loading C extension modules
1 parent dd8f05f commit e324003

File tree

7 files changed

+389
-24
lines changed

7 files changed

+389
-24
lines changed

Include/internal/pycore_ceval.h

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,50 @@ extern int _PyEval_ThreadsInitialized(void);
130130
extern void _PyEval_InitGIL(PyThreadState *tstate, int own_gil);
131131
extern void _PyEval_FiniGIL(PyInterpreterState *interp);
132132

133-
extern void _PyEval_AcquireLock(PyThreadState *tstate);
133+
// Acquire the GIL and return 1. In free-threaded builds, this function may
134+
// return 0 to indicate that the GIL was disabled and therefore not acquired.
135+
extern int _PyEval_AcquireLock(PyThreadState *tstate);
136+
134137
extern void _PyEval_ReleaseLock(PyInterpreterState *, PyThreadState *);
135138

139+
#ifdef Py_GIL_DISABLED
140+
// Returns 0 or 1 if the GIL for the given thread's interpreter is disabled or
141+
// enabled, respectively.
142+
//
143+
// The enabled state of the GIL will not change while one or more threads are
144+
// attached.
145+
extern int _PyEval_IsGILEnabled(PyThreadState *tstate);
146+
147+
// Enable or disable the GIL used by the interpreter that owns tstate, which
148+
// must be the current thread. This may affect other interpreters, if the GIL
149+
// is shared. All three functions will be no-ops (and return 0) if the
150+
// interpreter's `enable_gil' config is not _PyConfig_GIL_DEFAULT.
151+
//
152+
// Every call to _PyEval_EnableGILTransient() must be paired with exactly one
153+
// call to either _PyEval_EnableGILPermanent() or
154+
// _PyEval_DisableGIL(). _PyEval_EnableGILPermanent() and _PyEval_DisableGIL()
155+
// must only be called while the GIL is enabled from a call to
156+
// _PyEval_EnableGILTransient().
157+
//
158+
// _PyEval_EnableGILTransient() returns 1 if it enabled the GIL, or 0 if the
159+
// GIL was already enabled, whether transiently or permanently. The caller will
160+
// hold the GIL upon return.
161+
//
162+
// _PyEval_EnableGILPermanent() returns 1 if it permanently enabled the GIL
163+
// (which must already be enabled), or 0 if it was already permanently
164+
// enabled. Once _PyEval_EnableGILPermanent() has been called once, all
165+
// subsequent calls to any of the three functions will be no-ops.
166+
//
167+
// _PyEval_DisableGIL() returns 1 if it disabled the GIL, or 0 if the GIL was
168+
// kept enabled because of another request, whether transient or permanent.
169+
//
170+
// All three functions must be called by an attached thread (this implies that
171+
// if the GIL is enabled, the current thread must hold it).
172+
extern int _PyEval_EnableGILTransient(PyThreadState *tstate);
173+
extern int _PyEval_EnableGILPermanent(PyThreadState *tstate);
174+
extern int _PyEval_DisableGIL(PyThreadState *state);
175+
#endif
176+
136177
extern void _PyEval_DeactivateOpCache(void);
137178

138179

Include/internal/pycore_gil.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,20 @@ extern "C" {
2121

2222
struct _gil_runtime_state {
2323
#ifdef Py_GIL_DISABLED
24-
/* Whether or not this GIL is being used. Can change from 0 to 1 at runtime
25-
if, for example, a module that requires the GIL is loaded. */
24+
/* If this GIL is disabled, enabled == 0.
25+
26+
If this GIL is enabled transiently (most likely to initialize a module
27+
of unknown safety), enabled indicates the number of active transient
28+
requests.
29+
30+
If this GIL is enabled permanently, enabled == INT_MAX.
31+
32+
It must not be modified directly; use _PyEval_EnableGILTransiently(),
33+
_PyEval_EnableGILPermanently(), and _PyEval_DisableGIL()
34+
35+
It is always read and written atomically, but a thread can assume its
36+
value will be stable as long as that thread is attached or knows that no
37+
other threads are attached (e.g., during a stop-the-world.). */
2638
int enabled;
2739
#endif
2840
/* microseconds (the Python API uses seconds, though) */

Include/internal/pycore_import.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ struct _import_state {
8383
This is initialized lazily in PyState_AddModule(), which is also
8484
where modules get added. */
8585
PyObject *modules_by_index;
86+
#ifdef Py_GIL_DISABLED
87+
/* This list contains GIL slots (see Py_mod_gil) for modules that may be
88+
reinitialized by reload_singlephase_extension(). It is indexed the same
89+
way as modules_by_index. */
90+
PyObject *module_gil_by_index;
91+
#endif
8692
/* importlib module._bootstrap */
8793
PyObject *importlib;
8894
/* override for config->use_frozen_modules (for tests)
@@ -206,6 +212,22 @@ extern int _PyImport_CheckSubinterpIncompatibleExtensionAllowed(
206212
// Export for '_testinternalcapi' shared extension
207213
PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename);
208214

215+
#ifdef Py_GIL_DISABLED
216+
extern int _PyImport_SetModuleGIL(PyObject *module, void *gil);
217+
extern void *_PyImport_GetModuleDefGIL(PyModuleDef *def);
218+
219+
// Assuming that the GIL is enabled from a call to
220+
// _PyEval_EnableGILTransient(), resolve the transient request depending on the
221+
// state of the module argument:
222+
// - If module is NULL or a PyModuleObject with md_gil == Py_MOD_GIL_NOT_USED,
223+
// call _PyEval_DisableGIL().
224+
// - Otherwise, call _PyEval_EnableGILPermanent(). If the GIL was not already
225+
// enabled permanently, issue a warning referencing the module's name.
226+
//
227+
// This function may raise an exception.
228+
extern int _PyImport_CheckGILForModule(PyObject *module, PyObject *module_name);
229+
#endif
230+
209231
#ifdef __cplusplus
210232
}
211233
#endif

Python/ceval_gil.c

Lines changed: 151 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,16 @@ static void recreate_gil(struct _gil_runtime_state *gil)
205205
}
206206
#endif
207207

208+
static void
209+
drop_gil_impl(struct _gil_runtime_state *gil)
210+
{
211+
MUTEX_LOCK(gil->mutex);
212+
_Py_ANNOTATE_RWLOCK_RELEASED(&gil->locked, /*is_write=*/1);
213+
_Py_atomic_store_int_relaxed(&gil->locked, 0);
214+
COND_SIGNAL(gil->cond);
215+
MUTEX_UNLOCK(gil->mutex);
216+
}
217+
208218
static void
209219
drop_gil(PyInterpreterState *interp, PyThreadState *tstate)
210220
{
@@ -220,7 +230,7 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate)
220230

221231
struct _gil_runtime_state *gil = ceval->gil;
222232
#ifdef Py_GIL_DISABLED
223-
if (!gil->enabled) {
233+
if (!_Py_atomic_load_int_relaxed(&gil->enabled)) {
224234
return;
225235
}
226236
#endif
@@ -236,11 +246,7 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate)
236246
_Py_atomic_store_ptr_relaxed(&gil->last_holder, tstate);
237247
}
238248

239-
MUTEX_LOCK(gil->mutex);
240-
_Py_ANNOTATE_RWLOCK_RELEASED(&gil->locked, /*is_write=*/1);
241-
_Py_atomic_store_int_relaxed(&gil->locked, 0);
242-
COND_SIGNAL(gil->cond);
243-
MUTEX_UNLOCK(gil->mutex);
249+
drop_gil_impl(gil);
244250

245251
#ifdef FORCE_SWITCHING
246252
/* We check tstate first in case we might be releasing the GIL for
@@ -275,8 +281,10 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate)
275281
276282
The function saves errno at entry and restores its value at exit.
277283
278-
tstate must be non-NULL. */
279-
static void
284+
tstate must be non-NULL.
285+
286+
Returns 1 if the GIL was acquired, or 0 if not. */
287+
static int
280288
take_gil(PyThreadState *tstate)
281289
{
282290
int err = errno;
@@ -300,8 +308,8 @@ take_gil(PyThreadState *tstate)
300308
PyInterpreterState *interp = tstate->interp;
301309
struct _gil_runtime_state *gil = interp->ceval.gil;
302310
#ifdef Py_GIL_DISABLED
303-
if (!gil->enabled) {
304-
return;
311+
if (!_Py_atomic_load_int_relaxed(&gil->enabled)) {
312+
return 0;
305313
}
306314
#endif
307315

@@ -346,6 +354,17 @@ take_gil(PyThreadState *tstate)
346354
}
347355
}
348356

357+
#ifdef Py_GIL_DISABLED
358+
if (!_Py_atomic_load_int_relaxed(&gil->enabled)) {
359+
// Another thread disabled the GIL between our check above and
360+
// now. Don't take the GIL, signal any other waiting threads, and
361+
// return 0.
362+
COND_SIGNAL(gil->cond);
363+
MUTEX_UNLOCK(gil->mutex);
364+
return 0;
365+
}
366+
#endif
367+
349368
#ifdef FORCE_SWITCHING
350369
/* This mutex must be taken before modifying gil->last_holder:
351370
see drop_gil(). */
@@ -387,6 +406,7 @@ take_gil(PyThreadState *tstate)
387406
MUTEX_UNLOCK(gil->mutex);
388407

389408
errno = err;
409+
return 1;
390410
}
391411

392412
void _PyEval_SetSwitchInterval(unsigned long microseconds)
@@ -451,7 +471,8 @@ init_own_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil)
451471
{
452472
assert(!gil_created(gil));
453473
#ifdef Py_GIL_DISABLED
454-
gil->enabled = _PyInterpreterState_GetConfig(interp)->enable_gil == _PyConfig_GIL_ENABLE;
474+
const PyConfig *config = _PyInterpreterState_GetConfig(interp);
475+
gil->enabled = config->enable_gil == _PyConfig_GIL_ENABLE ? INT_MAX : 0;
455476
#endif
456477
create_gil(gil);
457478
assert(gil_created(gil));
@@ -545,11 +566,11 @@ PyEval_ReleaseLock(void)
545566
drop_gil(tstate->interp, tstate);
546567
}
547568

548-
void
569+
int
549570
_PyEval_AcquireLock(PyThreadState *tstate)
550571
{
551572
_Py_EnsureTstateNotNULL(tstate);
552-
take_gil(tstate);
573+
return take_gil(tstate);
553574
}
554575

555576
void
@@ -1011,6 +1032,123 @@ _PyEval_InitState(PyInterpreterState *interp)
10111032
_gil_initialize(&interp->_gil);
10121033
}
10131034

1035+
#ifdef Py_GIL_DISABLED
1036+
int
1037+
_PyEval_IsGILEnabled(PyThreadState *tstate)
1038+
{
1039+
return tstate->interp->ceval.gil->enabled != 0;
1040+
}
1041+
1042+
int
1043+
_PyEval_EnableGILTransient(PyThreadState *tstate)
1044+
{
1045+
if (_PyInterpreterState_GetConfig(tstate->interp)->enable_gil !=
1046+
_PyConfig_GIL_DEFAULT) {
1047+
return 0;
1048+
}
1049+
struct _gil_runtime_state *gil = tstate->interp->ceval.gil;
1050+
1051+
int enabled = _Py_atomic_load_int_relaxed(&gil->enabled);
1052+
if (enabled == INT_MAX) {
1053+
// The GIL is already enabled permanently.
1054+
return 0;
1055+
}
1056+
if (enabled == INT_MAX - 1) {
1057+
Py_FatalError("Too many transient requests to enable the GIL");
1058+
}
1059+
if (enabled > 0) {
1060+
// If enabled is nonzero, we know we hold the GIL. This means that no
1061+
// other threads are attached, and nobody else can be concurrently
1062+
// mutating it.
1063+
_Py_atomic_store_int_relaxed(&gil->enabled, enabled + 1);
1064+
return 0;
1065+
}
1066+
1067+
// Enabling the GIL changes what it means to be an "attached" thread. To
1068+
// safely make this transition, we:
1069+
// 1. Detach the current thread.
1070+
// 2. Stop the world to detach (and suspend) all other threads.
1071+
// 3. Enable the GIL, if nobody else did between our check above and when
1072+
// our stop-the-world begins.
1073+
// 4. Start the world.
1074+
// 5. Attach the current thread. Other threads may attach and hold the GIL
1075+
// before this thread, which is harmless.
1076+
_PyThreadState_Detach(tstate);
1077+
1078+
// This could be an interpreter-local stop-the-world in situations where we
1079+
// know that this interpreter's GIL is not shared, and that it won't become
1080+
// shared before the stop-the-world begins. For now, we always stop all
1081+
// interpreters for simplicity.
1082+
_PyEval_StopTheWorldAll(&_PyRuntime);
1083+
1084+
enabled = _Py_atomic_load_int_relaxed(&gil->enabled);
1085+
int this_thread_enabled = enabled == 0;
1086+
_Py_atomic_store_int_relaxed(&gil->enabled, enabled + 1);
1087+
1088+
_PyEval_StartTheWorldAll(&_PyRuntime);
1089+
_PyThreadState_Attach(tstate);
1090+
1091+
return this_thread_enabled;
1092+
}
1093+
1094+
int
1095+
_PyEval_EnableGILPermanent(PyThreadState *tstate)
1096+
{
1097+
if (_PyInterpreterState_GetConfig(tstate->interp)->enable_gil !=
1098+
_PyConfig_GIL_DEFAULT) {
1099+
return 0;
1100+
}
1101+
1102+
struct _gil_runtime_state *gil = tstate->interp->ceval.gil;
1103+
assert(current_thread_holds_gil(gil, tstate));
1104+
1105+
int enabled = _Py_atomic_load_int_relaxed(&gil->enabled);
1106+
if (enabled == INT_MAX) {
1107+
return 0;
1108+
}
1109+
1110+
_Py_atomic_store_int_relaxed(&gil->enabled, INT_MAX);
1111+
return 1;
1112+
}
1113+
1114+
int
1115+
_PyEval_DisableGIL(PyThreadState *tstate)
1116+
{
1117+
if (_PyInterpreterState_GetConfig(tstate->interp)->enable_gil !=
1118+
_PyConfig_GIL_DEFAULT) {
1119+
return 0;
1120+
}
1121+
1122+
struct _gil_runtime_state *gil = tstate->interp->ceval.gil;
1123+
assert(current_thread_holds_gil(gil, tstate));
1124+
1125+
int enabled = _Py_atomic_load_int_relaxed(&gil->enabled);
1126+
if (enabled == INT_MAX) {
1127+
return 0;
1128+
}
1129+
1130+
assert(enabled >= 1);
1131+
enabled--;
1132+
1133+
// Disabling the GIL is much simpler than enabling it, since we know we are
1134+
// the only attached thread. Other threads may start free-threading as soon
1135+
// as this store is complete, if it sets gil->enabled to 0.
1136+
_Py_atomic_store_int_relaxed(&gil->enabled, enabled);
1137+
1138+
if (enabled == 0) {
1139+
// We're attached, so we know the GIL will remain disabled until at
1140+
// least the next time we detach, which must be after this function
1141+
// returns.
1142+
//
1143+
// Drop the GIL, which will wake up any threads waiting in take_gil()
1144+
// and let them resume execution without the GIL.
1145+
drop_gil_impl(gil);
1146+
return 1;
1147+
}
1148+
return 0;
1149+
}
1150+
#endif
1151+
10141152

10151153
/* Do periodic things, like check for signals and async I/0.
10161154
* We need to do reasonably frequently, but not too frequently.

0 commit comments

Comments
 (0)