diff --git a/Python/import.c b/Python/import.c index a5ea0e2f81df57a..6956dd579189633 100644 --- a/Python/import.c +++ b/Python/import.c @@ -815,6 +815,8 @@ static int clear_singlephase_extension(PyInterpreterState *interp, // Currently, this is only used for testing. // (See _testinternalcapi.clear_extension().) +// If adding another use uses, careful about modules that import themselves +// recursively (see gh-123880) int _PyImport_ClearExtension(PyObject *name, PyObject *filename) { @@ -1322,12 +1324,16 @@ _extensions_cache_set(PyObject *path, PyObject *name, value = entry == NULL ? NULL : (struct extensions_cache_value *)entry->value; - /* We should never be updating an existing cache value. */ - assert(value == NULL); if (value != NULL) { - PyErr_Format(PyExc_SystemError, - "extension module %R is already cached", name); - goto finally; + /* gh-123880: If there's an existing cache value, it means a module + * is being imported recursively from its PyInit_* function. + * (The init function presumably handles returning a partially + * constructed module in such a case.) + * We can reuse the existing cache value; it is owned byt the cache. + * (Entries get removed from it in exceptional circumstances, + * after interpreter shutdown, and in runtime shutdown.) + */ + goto finally_oldvalue; } newvalue = alloc_extensions_cache_value(); if (newvalue == NULL) { @@ -1392,6 +1398,7 @@ _extensions_cache_set(PyObject *path, PyObject *name, cleanup_old_cached_def(&olddefbase); } +finally_oldvalue: extensions_lock_release(); if (key != NULL) { hashtable_destroy_str(key); @@ -2128,6 +2135,7 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0, } +// Used in _PyImport_ClearExtension; see notes there static int clear_singlephase_extension(PyInterpreterState *interp, PyObject *name, PyObject *path)