Skip to content

Commit

Permalink
Add Py_mod_gil module slot
Browse files Browse the repository at this point in the history
  • Loading branch information
swtaarrs committed Mar 12, 2024
1 parent 2731913 commit feda79c
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 2 deletions.
25 changes: 25 additions & 0 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,31 @@ The available slot types are:
.. versionadded:: 3.12
.. c:macro: Py_mod_gil
Specifies one of the following values:
.. c:macro:: Py_MOD_GIL_USED
The module assumes the presence of the global interpreter lock (GIL), and
may access global state without synchronization.
.. c:macro:: Py_MOD_GIL_NOT_USED
The module is safe to run without an active GIL.
This slot is only used by Python builds configured with
:option:`--disable-gil`, and determines whether or not importing this module
will cause the GIL to be automatically enabled. See :envvar:`PYTHON_GIL` and
:option:`-X gil <-X>` for more detail.
Multiple ``Py_mod_gil`` slots may not be specified in one module definition.
If ``Py_mod_gil`` is not specified, the import machinery defaults to
``Py_MOD_GIL_USED``.
.. versionadded: 3.13
See :PEP:`489` for more details on multi-phase initialization.
Low-level module creation functions
Expand Down
9 changes: 9 additions & 0 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ extern void _PyEval_FiniGIL(PyInterpreterState *interp);
extern void _PyEval_AcquireLock(PyThreadState *tstate);
extern void _PyEval_ReleaseLock(PyInterpreterState *, PyThreadState *);

#ifdef Py_GIL_DISABLED
// Enable the GIL used by the given interpreter. This may affect other
// interpreters, if the GIL is shared.
//
// Returns 1 if the GIL was disabled coming into this call, or 0 if it was
// already enabled.
extern int _PyEval_EnableGIL(PyInterpreterState *);
#endif

extern void _PyEval_DeactivateOpCache(void);


Expand Down
12 changes: 11 additions & 1 deletion Include/moduleobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,13 @@ struct PyModuleDef_Slot {
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030c0000
# define Py_mod_multiple_interpreters 3
#endif
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
# define Py_mod_gil 4
#endif


#ifndef Py_LIMITED_API
#define _Py_mod_LAST_SLOT 3
#define _Py_mod_LAST_SLOT 4
#endif

#endif /* New in 3.5 */
Expand All @@ -90,6 +94,12 @@ struct PyModuleDef_Slot {
# define Py_MOD_PER_INTERPRETER_GIL_SUPPORTED ((void *)2)
#endif

/* for Py_mod_gil: */
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
# define Py_MOD_GIL_USED ((void *)0)
# define Py_MOD_GIL_NOT_USED ((void *))
#endif

struct PyModuleDef {
PyModuleDef_Base m_base;
const char* m_name;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Multi-phase init extension modules may indicate to the runtime that they can
run without the GIL by providing ``Py_MOD_GIL_NOT_USED`` for the
``Py_mod_gil`` slot. Loading extensions that do not provide this slot will,
by default, enable the GIL for the remainder of the current process.
33 changes: 32 additions & 1 deletion Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "Python.h"
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _PyEval_EnableGIL()
#include "pycore_interp.h" // PyInterpreterState.importlib
#include "pycore_modsupport.h" // _PyModule_CreateInitialized()
#include "pycore_moduleobject.h" // _PyModule_GetDef()
Expand Down Expand Up @@ -248,7 +249,9 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
PyObject *nameobj;
PyObject *m = NULL;
int has_multiple_interpreters_slot = 0;
void *multiple_interpreters = (void *)0;
void *multiple_interpreters = (void *)Py_MOD_GIL_USED;
int has_gil_slot = 0;
void *gil_slot = (void *)0;
int has_execution_slots = 0;
const char *name;
int ret;
Expand Down Expand Up @@ -303,6 +306,17 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
multiple_interpreters = cur_slot->value;
has_multiple_interpreters_slot = 1;
break;
case Py_mod_gil:
if (has_gil_slot) {
PyErr_Format(
PyExc_SystemError,
"module %s has more than one 'gil' slot",
name);
goto error;
}
gil_slot = cur_slot->value;
has_gil_slot = 1;
break;
default:
assert(cur_slot->slot < 0 || cur_slot->slot > _Py_mod_LAST_SLOT);
PyErr_Format(
Expand Down Expand Up @@ -333,6 +347,22 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
goto error;
}

#ifdef Py_GIL_DISABLED
/* Loading an extension that doesn't need the GIL in a normal build is
fine, so only check in free-threaded builds. */
if (gil_slot == Py_MOD_GIL_USED && _PyEval_EnableGIL(interp)) {
PyErr_WarnFormat(
PyExc_RuntimeWarning,
1,
"The global interpreter lock (GIL) has been enabled to load module "
"'%s', which has not declared that it can run safely without the "
"GIL. To override this behavior and keep the GIL disabled, run "
"with PYTHON_GIL=0 or -Xgil=0.",
name
);
}
#endif

if (create) {
m = create(spec, def);
if (m == NULL) {
Expand Down Expand Up @@ -458,6 +488,7 @@ PyModule_ExecDef(PyObject *module, PyModuleDef *def)
}
break;
case Py_mod_multiple_interpreters:
case Py_mod_gil:
/* handled in PyModule_FromDefAndSpec2 */
break;
default:
Expand Down
31 changes: 31 additions & 0 deletions Python/ceval_gil.c
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,37 @@ _PyEval_InitState(PyInterpreterState *interp)
_gil_initialize(&interp->_gil);
}

int
_PyEval_EnableGIL(PyInterpreterState *interp)
{
#ifndef Py_GIL_DISABLED
Py_FatalError("_PyEval_EnableGIL() not supported in normal builds");
#else
struct _gil_runtime_state *gil = interp->ceval.gil;
if (gil->enabled) {
return 0;
}

int own_gil = interp->ceval.own_gil;
if (own_gil) {
_PyEval_StopTheWorld(interp);
}
else {
_PyEval_StopTheWorldAll(&_PyRuntime);
}

gil->enabled = 1;

if (own_gil) {
_PyEval_StartTheWorld(interp);
}
else {
_PyEval_StartTheWorldAll(&_PyRuntime);
}
return 1;
#endif
}


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

0 comments on commit feda79c

Please sign in to comment.