Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 853163d

Browse filesBrowse files
authored
gh-116322: Enable the GIL while loading C extension modules (#118560)
Add the ability to enable/disable the GIL at runtime, and use that in the C module loading code. We can't know before running a module init function if it supports free-threading, so the GIL is temporarily enabled before doing so. If the module declares support for running without the GIL, the GIL is later disabled. Otherwise, the GIL is permanently enabled, and will never be disabled again for the life of the current interpreter.
1 parent 60bd111 commit 853163d
Copy full SHA for 853163d

File tree

Expand file treeCollapse file tree

9 files changed

+352
-32
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+352
-32
lines changed

‎Include/internal/pycore_ceval.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_ceval.h
+46-1Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,54 @@ extern int _PyEval_ThreadsInitialized(void);
131131
extern void _PyEval_InitGIL(PyThreadState *tstate, int own_gil);
132132
extern void _PyEval_FiniGIL(PyInterpreterState *interp);
133133

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

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

139184

‎Include/internal/pycore_gil.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_gil.h
+14-2Lines 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

Copy file name to clipboardExpand all lines: Include/internal/pycore_import.h
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,19 @@ extern int _PyImport_CheckSubinterpIncompatibleExtensionAllowed(
206206
// Export for '_testinternalcapi' shared extension
207207
PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename);
208208

209+
#ifdef Py_GIL_DISABLED
210+
// Assuming that the GIL is enabled from a call to
211+
// _PyEval_EnableGILTransient(), resolve the transient request depending on the
212+
// state of the module argument:
213+
// - If module is NULL or a PyModuleObject with md_gil == Py_MOD_GIL_NOT_USED,
214+
// call _PyEval_DisableGIL().
215+
// - Otherwise, call _PyEval_EnableGILPermanent(). If the GIL was not already
216+
// enabled permanently, issue a warning referencing the module's name.
217+
//
218+
// This function may raise an exception.
219+
extern int _PyImport_CheckGILForModule(PyObject *module, PyObject *module_name);
220+
#endif
221+
209222
#ifdef __cplusplus
210223
}
211224
#endif
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
In ``--disable-gil`` builds, the GIL will be enabled while loading C extension modules. If the module indicates that it supports running without the GIL, the GIL will be disabled once loading is complete. Otherwise, the GIL will remain enabled for the remainder of the interpreter's lifetime. This behavior does not apply if the GIL has been explicitly enabled or disabled with ``PYTHON_GIL`` or ``-Xgil``.

‎PC/_wmimodule.cpp

Copy file name to clipboardExpand all lines: PC/_wmimodule.cpp
+9-3Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,12 +362,18 @@ static PyMethodDef wmi_functions[] = {
362362
{ NULL, NULL, 0, NULL }
363363
};
364364

365+
static PyModuleDef_Slot wmi_slots[] = {
366+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
367+
{0, NULL},
368+
};
369+
365370
static PyModuleDef wmi_def = {
366371
PyModuleDef_HEAD_INIT,
367372
"_wmi",
368-
NULL, // doc
369-
0, // m_size
370-
wmi_functions
373+
NULL, // doc
374+
0, // m_size
375+
wmi_functions, // m_methods
376+
wmi_slots, // m_slots
371377
};
372378

373379
extern "C" {

‎Python/ceval_gil.c

Copy file name to clipboardExpand all lines: Python/ceval_gil.c
+145-13Lines changed: 145 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,117 @@ _PyEval_InitState(PyInterpreterState *interp)
10111032
_gil_initialize(&interp->_gil);
10121033
}
10131034

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

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

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.