Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fa92cb4
Add Py_mod_gil module slot
swtaarrs Mar 12, 2024
7edf796
Merge remote-tracking branch 'upstream/main' into cpython-mod-gil
swtaarrs Mar 22, 2024
450aa41
Some review comments from Eric
swtaarrs Mar 27, 2024
6bbd281
Merge remote-tracking branch 'upstream/main' into cpython-mod-gil
swtaarrs Apr 3, 2024
47b9e26
Fix enabling the GIL, also support disabling the GIL
swtaarrs Apr 15, 2024
f7c3e50
Merge remote-tracking branch 'upstream/main' into cpython-mod-gil
swtaarrs Apr 15, 2024
6c198e4
Fix module size in test_objecttypes
swtaarrs Apr 15, 2024
554c5b4
Add missing : in Py_mod_gil documentation
swtaarrs Apr 15, 2024
cd187a0
Merge remote-tracking branch 'upstream/main' into cpython-mod-gil
swtaarrs Apr 23, 2024
8057478
From Eric: better loop, move _PyImport_CheckGILForModule
swtaarrs Apr 23, 2024
a8f0943
Merge remote-tracking branch 'upstream/main' into cpython-mod-gil
swtaarrs Apr 26, 2024
bbb949e
Remove code to enable/disable the GIL (it will go in a different PR)
swtaarrs Apr 30, 2024
7f1205e
Don't put PyModule_SetGIL() in the limited API
swtaarrs Apr 26, 2024
f8b02b3
Set m->md_gil in PyModule_FromDefAndSpec2, rename/guard PyModule_SetG…
swtaarrs May 1, 2024
d2bad05
Fix limited API version for Py_mod_gil
swtaarrs May 1, 2024
d7d59f0
Update NEWS entry for behavioral changes
swtaarrs Apr 30, 2024
ccd6e00
Mark all modules as not using GIL
swtaarrs May 1, 2024
8846586
Merge remote-tracking branch 'upstream/main' into HEAD
swtaarrs May 1, 2024
99bc5cb
Fix 'gil_slot set but not used' warning
swtaarrs May 1, 2024
6882c13
Patch generator for Python-ast.c instead of just the file itself
swtaarrs May 1, 2024
da63df1
Update limited API version for _scproxy.c
swtaarrs May 1, 2024
2998def
Fix _testconsole.c for Windows build
swtaarrs May 1, 2024
77d1652
Fix winsound.c for Windows build
swtaarrs May 2, 2024
d1fe0cc
Mark more modules as not using the GIL
swtaarrs May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 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 depends on 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.
Comment thread
swtaarrs marked this conversation as resolved.

This slot is ignored by Python builds not configured with
:option:`--disable-gil`. Otherwise, it 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 Expand Up @@ -609,6 +634,19 @@ state:

.. versionadded:: 3.9

.. c:function:: int PyModule_SetGIL(PyObject *module, void *gil)

In Python builds not configured with :option:`--disable-gil`, do
nothing. Otherwise, indicate that *module* does or does not support running
without the global interpreter lock (GIL), using one of the values from
:c:macro:`Py_mod_gil`. It must be called during *module*'s initialization
function. If this function is not called during module initialization, the
import machinery assumes the module does not support running without the
GIL.
Return ``-1`` on error, ``0`` on success.

.. versionadded:: 3.13


Module lookup
^^^^^^^^^^^^^
Expand Down
36 changes: 35 additions & 1 deletion Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,43 @@ extern int _PyEval_ThreadsInitialized(void);
extern void _PyEval_InitGIL(PyThreadState *tstate, int own_gil);
extern void _PyEval_FiniGIL(PyInterpreterState *interp);

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

extern void _PyEval_ReleaseLock(PyInterpreterState *, PyThreadState *);

#ifdef Py_GIL_DISABLED
// Enable or disable the GIL used by the interpreter that owns tstate, which
// must be the current thread. This may affect other interpreters, if the GIL
// is shared. All three functions will be no-ops (and return 0) if the
// interpreter's `enable_gil' config is not _PyConfig_GIL_DEFAULT.
//
// Every call to _PyEval_EnableGILTransient() must be paired with exactly one
// call to either _PyEval_EnableGILPermanent() or
// _PyEval_DisableGIL(). _PyEval_EnableGILPermanent() and _PyEval_DisableGIL()
// must only be called while the GIL is enabled from a call to
// _PyEval_EnableGILTransient().
//
// _PyEval_EnableGILTransient() returns 1 if it enabled the GIL, or 0 if the
// GIL was already enabled, whether transiently or permanently. The caller will
// hold the GIL upon return.
//
// _PyEval_EnableGILPermanent() returns 1 if it permanently enabled the GIL
// (which must already be enabled), or 0 if it was already permanently
// enabled. Once _PyEval_EnableGILPermanent() has been called once, all
// subsequent calls to any of the three functions will be no-ops.
//
// _PyEval_DisableGIL() returns 1 if it disabled the GIL, or 0 if the GIL was
// kept enabled because of another request, whether transient or permanent.
//
// All three functions must be called by an attached thread (this implies that
// if the GIL is enabled, the current thread must hold it).
extern int _PyEval_EnableGILTransient(PyThreadState *tstate);
extern int _PyEval_EnableGILPermanent(PyThreadState *tstate);
extern int _PyEval_DisableGIL(PyThreadState *state);
#endif

extern void _PyEval_DeactivateOpCache(void);


Expand Down
16 changes: 14 additions & 2 deletions Include/internal/pycore_gil.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,20 @@ extern "C" {

struct _gil_runtime_state {
#ifdef Py_GIL_DISABLED
/* Whether or not this GIL is being used. Can change from 0 to 1 at runtime
if, for example, a module that requires the GIL is loaded. */
/* If this GIL is disabled, enabled == 0.

If this GIL is enabled transiently (most likely to initialize a module
of unknown safety), enabled indicates the number of active transient
requests.

If this GIL is enabled permanently, enabled == INT_MAX.

It must not be modified directly; use _PyEval_EnableGILTransiently(),
_PyEval_EnableGILPermanently(), and _PyEval_DisableGIL()

It is always read and written atomically, but a thread can assume its
value will be stable as long as that thread is attached or knows that no
other threads are attached (e.g., during a stop-the-world.). */
int enabled;
#endif
/* microseconds (the Python API uses seconds, though) */
Expand Down
11 changes: 11 additions & 0 deletions Include/internal/pycore_import.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ extern int _PyImport_CheckSubinterpIncompatibleExtensionAllowed(
// Export for '_testinternalcapi' shared extension
PyAPI_FUNC(int) _PyImport_ClearExtension(PyObject *name, PyObject *filename);

#ifdef Py_GIL_DISABLED
// Assuming that the GIL is enabled from a call to
// _PyEval_EnableGILTransient(), either enable the GIL permanently or disable
// the GIL, depending on the value of the gil argument, which should be one of
// the values of the Py_mod_gil slot.
//
// If the GIL is enabled permanently, a warning will be issued referencing the
// module's name.
extern void _PyImport_CheckGILForModule(void *gil, PyObject *module_name);
#endif

#ifdef __cplusplus
}
#endif
Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_moduleobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ typedef struct {
PyObject *md_weaklist;
// for logging purposes after md_dict is cleared
PyObject *md_name;
#ifdef Py_GIL_DISABLED
void *md_gil;
#endif
Comment thread
ericsnowcurrently marked this conversation as resolved.
} PyModuleObject;

static inline PyModuleDef* _PyModule_GetDef(PyObject *mod) {
Expand Down
13 changes: 12 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,13 @@ 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 *)1)
PyAPI_FUNC(int) PyModule_SetGIL(PyObject *module, void *gil);
#endif
Comment thread
swtaarrs marked this conversation as resolved.

struct PyModuleDef {
PyModuleDef_Base m_base;
const char* m_name;
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1595,7 +1595,10 @@ def get_gen(): yield 1
check(int(PyLong_BASE**2-1), vsize('') + 2*self.longdigit)
check(int(PyLong_BASE**2), vsize('') + 3*self.longdigit)
# module
check(unittest, size('PnPPP'))
if support.Py_GIL_DISABLED:
check(unittest, size('PPPPPP'))
else:
check(unittest, size('PPPPP'))
# None
check(None, size(''))
# NotImplementedType
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
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. In ``--disable-gil`` builds, loading extensions that do not provide this
slot will enable the GIL for the remainder of the current interpreter, unless
the GIL was explicitly disabled by ``PYTHON_GIL=0`` or ``-Xgil=0``.
44 changes: 41 additions & 3 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "Python.h"
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _PyEval_EnableGIL()
#include "pycore_initconfig.h" // _PyConfig_GIL_DEFAULT
#include "pycore_interp.h" // PyInterpreterState.importlib
#include "pycore_modsupport.h" // _PyModule_CreateInitialized()
#include "pycore_moduleobject.h" // _PyModule_GetDef()
Expand Down Expand Up @@ -247,6 +249,9 @@ _PyModule_CreateInitialized(PyModuleDef* module, int module_api_version)
}
}
m->md_def = module;
#ifdef Py_GIL_DISABLE
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Py_GIL_DISABLED.

m->md_gil = Py_MOD_GIL_USED;
#endif
return (PyObject*)m;
}

Expand All @@ -255,10 +260,12 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
{
PyModuleDef_Slot* cur_slot;
PyObject *(*create)(PyObject *, PyModuleDef*) = NULL;
PyObject *nameobj;
PyObject *nameobj = NULL;
PyObject *m = NULL;
int has_multiple_interpreters_slot = 0;
void *multiple_interpreters = (void *)0;
int has_gil_slot = 0;
void *gil_slot = Py_MOD_GIL_USED;
Comment thread
ericsnowcurrently marked this conversation as resolved.
int has_execution_slots = 0;
const char *name;
int ret;
Expand All @@ -268,7 +275,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio

nameobj = PyObject_GetAttrString(spec, "name");
if (nameobj == NULL) {
return NULL;
goto error;
}
name = PyUnicode_AsUTF8(nameobj);
if (name == NULL) {
Expand Down Expand Up @@ -313,6 +320,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 @@ -343,6 +361,12 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
goto error;
}

#ifdef Py_GIL_DISABLED
_PyImport_CheckGILForModule(gil_slot, nameobj);
#else
(void)gil_slot;
#endif

if (create) {
m = create(spec, def);
if (m == NULL) {
Expand Down Expand Up @@ -408,11 +432,24 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
return m;

error:
Py_DECREF(nameobj);
Py_XDECREF(nameobj);
Py_XDECREF(m);
return NULL;
}

int
PyModule_SetGIL(PyObject *module, void *gil)
{
if (!PyModule_Check(module)) {
PyErr_BadInternalCall();
return -1;
}
#ifdef Py_GIL_DISABLED
((PyModuleObject *)module)->md_gil = gil;
#endif
return 0;
}

int
PyModule_ExecDef(PyObject *module, PyModuleDef *def)
{
Expand Down Expand Up @@ -468,6 +505,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
Loading