@@ -815,6 +815,8 @@ static int clear_singlephase_extension(PyInterpreterState *interp,
815
815
816
816
// Currently, this is only used for testing.
817
817
// (See _testinternalcapi.clear_extension().)
818
+ // If adding another use uses, careful about modules that import themselves
819
+ // recursively (see gh-123880)
818
820
int
819
821
_PyImport_ClearExtension (PyObject * name , PyObject * filename )
820
822
{
@@ -1322,12 +1324,16 @@ _extensions_cache_set(PyObject *path, PyObject *name,
1322
1324
value = entry == NULL
1323
1325
? NULL
1324
1326
: (struct extensions_cache_value * )entry -> value ;
1325
- /* We should never be updating an existing cache value. */
1326
- assert (value == NULL );
1327
1327
if (value != NULL ) {
1328
- PyErr_Format (PyExc_SystemError ,
1329
- "extension module %R is already cached" , name );
1330
- goto finally ;
1328
+ /* gh-123880: If there's an existing cache value, it means a module is
1329
+ * being imported recursively from its PyInit_* or Py_mod_* function.
1330
+ * (That function presumably handles returning a partially
1331
+ * constructed module in such a case.)
1332
+ * We can reuse the existing cache value; it is owned byt the cache.
1333
+ * (Entries get removed from it in exceptional circumstances,
1334
+ * after interpreter shutdown, and in runtime shutdown.)
1335
+ */
1336
+ goto finally_oldvalue ;
1331
1337
}
1332
1338
newvalue = alloc_extensions_cache_value ();
1333
1339
if (newvalue == NULL ) {
@@ -1392,6 +1398,7 @@ _extensions_cache_set(PyObject *path, PyObject *name,
1392
1398
cleanup_old_cached_def (& olddefbase );
1393
1399
}
1394
1400
1401
+ finally_oldvalue :
1395
1402
extensions_lock_release ();
1396
1403
if (key != NULL ) {
1397
1404
hashtable_destroy_str (key );
@@ -2128,6 +2135,7 @@ import_run_extension(PyThreadState *tstate, PyModInitFunction p0,
2128
2135
}
2129
2136
2130
2137
2138
+ // Used in _PyImport_ClearExtension; see notes there
2131
2139
static int
2132
2140
clear_singlephase_extension (PyInterpreterState * interp ,
2133
2141
PyObject * name , PyObject * path )
0 commit comments