Skip to content

Commit 60960cb

Browse files
committed
Make _ast module state per-interpreter when building CPython core
- For Py_BUILD_CORE, the interpreter state now contains a lazily initialized pointer to _ast module state, which is cleared through _PyAST_Fini. - Elsewhere (for extension modules created by e.g. pegen tests), the state is process-global (static). To prevent issues, such an _ast module can only be loaded once per process. See PEP 630 for background info.
1 parent 4a2b65a commit 60960cb

File tree

3 files changed

+149
-12
lines changed

3 files changed

+149
-12
lines changed

‎Include/internal/pycore_interp.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ struct _is {
255255
struct _Py_async_gen_state async_gen;
256256
struct _Py_context_state context;
257257
struct _Py_exc_state exc_state;
258+
259+
// _ast module state, allocated on demand (see Python/Python-ast.c)
260+
struct _Py_ast_state *ast_state;
258261
};
259262

260263
/* Used by _PyImport_Cleanup() */

‎Parser/asdl_c.py‎

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,17 @@ def visitModule(self, mod):
10571057
self.emit("astmodule_exec(PyObject *m)", 0)
10581058
self.emit("{", 0)
10591059
self.emit('astmodulestate *state = get_ast_state(m);', 1)
1060+
self.emit("if (state == NULL) {", 1)
1061+
self.emit("return -1;", 2)
1062+
self.emit("}", 1)
1063+
self.emit("#ifndef Py_BUILD_CORE", 0)
1064+
self.emit('if (state->initialized) {', 1)
1065+
self.emit('PyErr_SetString(', 2)
1066+
self.emit('PyExc_ImportError,', 3)
1067+
self.emit('"cannot load non-core _ast module more than once per process");', 3)
1068+
self.emit("return -1;", 2)
1069+
self.emit('}', 1)
1070+
self.emit("#endif", 0)
10601071
self.emit("", 0)
10611072

10621073
self.emit("if (!init_types(state)) {", 1)
@@ -1089,7 +1100,7 @@ def visitModule(self, mod):
10891100
static struct PyModuleDef _astmodule = {
10901101
PyModuleDef_HEAD_INIT,
10911102
.m_name = "_ast",
1092-
// The _ast module uses a global state (global_ast_state).
1103+
// The _ast module uses a per-interpreter state (see get_global_ast_state)
10931104
.m_size = 0,
10941105
.m_slots = astmodule_slots,
10951106
};
@@ -1366,17 +1377,59 @@ def generate_module_def(f, mod):
13661377
module_state.add(tp)
13671378
state_strings = sorted(state_strings)
13681379
module_state = sorted(module_state)
1369-
f.write('typedef struct {\n')
1380+
f.write('typedef struct _Py_ast_state {\n')
13701381
f.write(' int initialized;\n')
13711382
for s in module_state:
13721383
f.write(' PyObject *' + s + ';\n')
13731384
f.write('} astmodulestate;\n\n')
13741385
f.write("""
13751386
// Forward declaration
13761387
static int init_types(astmodulestate *state);
1388+
static void fini_types(astmodulestate *state);
13771389
1378-
// bpo-41194, bpo-41261, bpo-41631: The _ast module uses a global state.
1379-
static astmodulestate global_ast_state = {0};
1390+
#ifdef Py_BUILD_CORE
1391+
1392+
// bpo-41194, bpo-41261, bpo-41631: The _ast module uses a per-interpreter
1393+
// state; if several _ast module objects are created, they share the state
1394+
// including all AST types.
1395+
// The state is allocated and initialized on demand.
1396+
1397+
static astmodulestate*
1398+
get_global_ast_state(void)
1399+
{
1400+
PyInterpreterState* interp = _PyInterpreterState_Get();
1401+
if (interp->ast_state == NULL) {
1402+
interp->ast_state = (astmodulestate*)PyMem_Calloc(1, sizeof(astmodulestate));
1403+
}
1404+
if (interp->ast_state == NULL) {
1405+
PyErr_NoMemory();
1406+
return NULL;
1407+
}
1408+
if (!init_types(interp->ast_state)) {
1409+
PyMem_Free(interp->ast_state);
1410+
interp->ast_state = NULL;
1411+
return NULL;
1412+
}
1413+
return interp->ast_state;
1414+
}
1415+
1416+
void
1417+
_PyAST_Fini(PyThreadState *tstate)
1418+
{
1419+
PyInterpreterState* interp = tstate->interp;
1420+
fini_types(interp->ast_state);
1421+
PyMem_Free(interp->ast_state);
1422+
interp->ast_state = NULL;
1423+
}
1424+
1425+
#else
1426+
1427+
// HACK: If we're not building CPython core (as happens in pegen tests),
1428+
// we use a static global module state object.
1429+
// In astmodule_exec, we limit such modules to be only importable once
1430+
// per process, so the state doesn't end up shared across interpreters.
1431+
1432+
static astmodulestate global_ast_state;
13801433
13811434
static astmodulestate*
13821435
get_global_ast_state(void)
@@ -1388,6 +1441,14 @@ def generate_module_def(f, mod):
13881441
return state;
13891442
}
13901443
1444+
void
1445+
_PyAST_Fini(PyThreadState *tstate)
1446+
{
1447+
fini_types(&global_ast_state);
1448+
}
1449+
1450+
#endif
1451+
13911452
static astmodulestate*
13921453
get_ast_state(PyObject* Py_UNUSED(module))
13931454
{
@@ -1398,9 +1459,12 @@ def generate_module_def(f, mod):
13981459
return state;
13991460
}
14001461
1401-
void _PyAST_Fini(PyThreadState *tstate)
1462+
static void
1463+
fini_types(astmodulestate* state)
14021464
{
1403-
astmodulestate* state = &global_ast_state;
1465+
if (state == NULL || !state->initialized) {
1466+
return;
1467+
}
14041468
""")
14051469
for s in module_state:
14061470
f.write(" Py_CLEAR(state->" + s + ');\n')
@@ -1452,6 +1516,9 @@ def write_source(f, mod):
14521516
f.write('#include "Python.h"\n')
14531517
f.write('#include "%s-ast.h"\n' % mod.name)
14541518
f.write('#include "structmember.h" // PyMemberDef\n')
1519+
f.write('#if Py_BUILD_CORE\n')
1520+
f.write('#include "pycore_interp.h" // _PyInterpreterState.ast_state\n')
1521+
f.write('#endif\n')
14551522
f.write('\n')
14561523

14571524
generate_module_def(f, mod)

‎Python/Python-ast.c‎

Lines changed: 73 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)