Skip to content

Commit 917df44

Browse files
committed
Handle lazy module hooks and children together
Resolve lazy __getattr__ and __dir__ hooks before calling them. Treat a hook already being resolved as unavailable so it can import a sibling. Bind a loaded child before removing its pending entry. Recheck the module dict when another thread may have completed the load. Reuse modules already in sys.modules during package cycles, and simplify child registration.
1 parent b78ebc9 commit 917df44

3 files changed

Lines changed: 138 additions & 165 deletions

File tree

‎Include/internal/pycore_import.h‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,11 @@ extern PyObject * _PyImport_GetAbsName(
3737
// Symbol is exported for the JIT on Windows builds.
3838
PyAPI_FUNC(PyObject *) _PyImport_LoadLazyImportTstate(
3939
PyThreadState *tstate, PyObject *lazy_import);
40-
typedef enum {
41-
_Py_LAZY_SUBMODULE_ERROR = -1,
42-
_Py_LAZY_SUBMODULE_NOT_FOUND = 0,
43-
_Py_LAZY_SUBMODULE_LOADED = 1,
44-
} _PyLazySubmoduleImportResult;
45-
extern _PyLazySubmoduleImportResult _PyImport_TryLoadLazySubmodule(
46-
PyObject *mod_name, PyObject *attr_name, PyObject **result);
40+
// Bind a pending child on module before removing its registration. Return a
41+
// new reference, NULL without an exception if absent, or NULL on error.
42+
// Set recheck_dict if a concurrent lookup may have already bound the child.
43+
extern PyObject * _PyImport_TryLoadLazySubmodule(
44+
PyObject *module, PyObject *attr_name, int *recheck_dict);
4745
extern PyObject * _PyImport_LazyImportModuleLevelObject(
4846
PyThreadState *tstate, PyObject *name, PyObject *builtins,
4947
PyObject *globals, PyObject *locals, PyObject *fromlist, int level);

‎Objects/moduleobject.c‎

Lines changed: 73 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include "pycore_fileutils.h" // _Py_wgetcwd
88
#include "pycore_import.h" // _PyImport_GetNextModuleIndex()
99
#include "pycore_interp.h" // PyInterpreterState.importlib
10-
#include "pycore_lazyimportobject.h" // _PyLazyImportObject_Check()
10+
#include "pycore_lazyimportobject.h" // PyLazyImport_CheckExact()
1111
#include "pycore_long.h" // _PyLong_GetOne()
1212
#include "pycore_modsupport.h" // _PyModule_CreateInitialized()
1313
#include "pycore_moduleobject.h" // _PyModule_GetDefOrNull()
@@ -1299,32 +1299,25 @@ _PyModule_IsPossiblyShadowing(PyObject *origin)
12991299
return result;
13001300
}
13011301

1302-
static PyObject *
1303-
try_load_lazy_submodule(PyModuleObject *m, PyObject *name)
1302+
static int
1303+
module_get_resolved_dict_item(PyObject *dict, PyObject *name, PyObject **result)
13041304
{
1305-
PyObject *mod_name;
1306-
int rc = PyDict_GetItemRef(m->md_dict, &_Py_ID(__name__), &mod_name);
1307-
if (rc <= 0) {
1308-
return NULL;
1309-
}
1310-
if (!PyUnicode_Check(mod_name)) {
1311-
Py_DECREF(mod_name);
1312-
return NULL;
1305+
int rc = PyDict_GetItemRef(dict, name, result);
1306+
if (rc <= 0 || !PyLazyImport_CheckExact(*result)) {
1307+
return rc;
13131308
}
1314-
PyObject *result = NULL;
1315-
_PyLazySubmoduleImportResult status =
1316-
_PyImport_TryLoadLazySubmodule(mod_name, name, &result);
1317-
Py_DECREF(mod_name);
1318-
if (status != _Py_LAZY_SUBMODULE_LOADED) {
1319-
assert(status == _Py_LAZY_SUBMODULE_ERROR ||
1320-
status == _Py_LAZY_SUBMODULE_NOT_FOUND);
1321-
return NULL;
1309+
PyThreadState *tstate = PyThreadState_GET();
1310+
if (_PyLazyImport_IsResolving(tstate, *result)) {
1311+
Py_CLEAR(*result);
1312+
return 0;
13221313
}
1323-
if (PyDict_SetItem(m->md_dict, name, result) < 0) {
1324-
Py_DECREF(result);
1325-
return NULL;
1314+
PyObject *value = _PyImport_LoadLazyImportTstate(tstate, *result);
1315+
Py_SETREF(*result, value);
1316+
if (value == NULL || PyDict_SetItem(dict, name, value) < 0) {
1317+
Py_CLEAR(*result);
1318+
return -1;
13261319
}
1327-
return result;
1320+
return 1;
13281321
}
13291322

13301323
PyObject*
@@ -1333,11 +1326,52 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
13331326
// When suppress=1, this function suppresses AttributeError.
13341327
PyObject *attr, *mod_name, *getattr;
13351328
attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
1329+
if (attr == NULL) {
1330+
if (suppress == 1) {
1331+
if (PyErr_Occurred()) {
1332+
// pass up non-AttributeError exception
1333+
return NULL;
1334+
}
1335+
}
1336+
else {
1337+
if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
1338+
// pass up non-AttributeError exception
1339+
return NULL;
1340+
}
1341+
PyErr_Clear();
1342+
}
1343+
assert(m->md_dict != NULL);
1344+
int recheck_dict;
1345+
attr = _PyImport_TryLoadLazySubmodule((PyObject *)m, name, &recheck_dict);
1346+
if (attr != NULL || PyErr_Occurred()) {
1347+
return attr;
1348+
}
1349+
// A concurrent load may have bound the child and removed its pending
1350+
// registration after our initial dictionary lookup.
1351+
if (recheck_dict) {
1352+
if (Py_TYPE(m) != &PyModule_Type) {
1353+
PyObject *descr = _PyType_LookupRef(Py_TYPE(m), name);
1354+
if (descr != NULL) {
1355+
// Preserve the fallback after a descriptor raised.
1356+
recheck_dict = Py_TYPE(descr)->tp_descr_get == NULL;
1357+
Py_DECREF(descr);
1358+
}
1359+
}
1360+
if (recheck_dict &&
1361+
PyDict_GetItemRef(m->md_dict, name, &attr) < 0) {
1362+
return NULL;
1363+
}
1364+
}
1365+
}
13361366
if (attr) {
13371367
if (PyLazyImport_CheckExact(attr)) {
13381368
// gh-144957: Module __getattr__ should get a chance to provide
13391369
// the attribute before resolving a lazy import placeholder.
1340-
if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__getattr__), &getattr) < 0) {
1370+
// Resolving __getattr__ itself must not invoke the hook.
1371+
getattr = NULL;
1372+
if (!_PyUnicode_EqualToASCIIString(name, "__getattr__") &&
1373+
module_get_resolved_dict_item(
1374+
m->md_dict, &_Py_ID(__getattr__), &getattr) < 0) {
13411375
Py_DECREF(attr);
13421376
return NULL;
13431377
}
@@ -1354,17 +1388,14 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
13541388
}
13551389
PyErr_Clear();
13561390
}
1357-
PyObject *new_value = _PyImport_LoadLazyImportTstate(
1358-
PyThreadState_GET(), attr);
1391+
PyThreadState *tstate = PyThreadState_GET();
1392+
if (suppress && _PyLazyImport_IsResolving(tstate, attr)) {
1393+
// Only direct reentry means this attribute is unavailable.
1394+
Py_DECREF(attr);
1395+
return NULL;
1396+
}
1397+
PyObject *new_value = _PyImport_LoadLazyImportTstate(tstate, attr);
13591398
if (new_value == NULL) {
1360-
if (suppress &&
1361-
PyErr_ExceptionMatches(PyExc_ImportCycleError)) {
1362-
// ImportCycleError is raised when a lazy object tries
1363-
// to import itself. In this case, the error should not
1364-
// propagate to the caller and instead treated as if the
1365-
// attribute doesn't exist.
1366-
PyErr_Clear();
1367-
}
13681399
Py_DECREF(attr);
13691400
return NULL;
13701401
}
@@ -1377,28 +1408,8 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
13771408
}
13781409
return attr;
13791410
}
1380-
if (suppress == 1) {
1381-
if (PyErr_Occurred()) {
1382-
// pass up non-AttributeError exception
1383-
return NULL;
1384-
}
1385-
}
1386-
else {
1387-
if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
1388-
// pass up non-AttributeError exception
1389-
return NULL;
1390-
}
1391-
PyErr_Clear();
1392-
}
1393-
assert(m->md_dict != NULL);
1394-
attr = try_load_lazy_submodule(m, name);
1395-
if (attr != NULL) {
1396-
return attr;
1397-
}
1398-
if (PyErr_Occurred()) {
1399-
return NULL;
1400-
}
1401-
if (PyDict_GetItemRef(m->md_dict, &_Py_ID(__getattr__), &getattr) < 0) {
1411+
if (module_get_resolved_dict_item(
1412+
m->md_dict, &_Py_ID(__getattr__), &getattr) < 0) {
14021413
return NULL;
14031414
}
14041415
if (getattr) {
@@ -1589,11 +1600,14 @@ module_dir(PyObject *self, PyObject *args)
15891600

15901601
if (dict != NULL) {
15911602
if (PyDict_Check(dict)) {
1592-
PyObject *dirfunc = PyDict_GetItemWithError(dict, &_Py_ID(__dir__));
1593-
if (dirfunc) {
1603+
PyObject *dirfunc;
1604+
int rc = module_get_resolved_dict_item(
1605+
dict, &_Py_ID(__dir__), &dirfunc);
1606+
if (rc > 0) {
15941607
result = _PyObject_CallNoArgs(dirfunc);
1608+
Py_DECREF(dirfunc);
15951609
}
1596-
else if (!PyErr_Occurred()) {
1610+
else if (rc == 0) {
15971611
result = PyDict_Keys(dict);
15981612
}
15991613
}

0 commit comments

Comments
 (0)