@@ -291,12 +291,18 @@ hashtable_unicode_compare(const void *key1, const void *key2)
291
291
It's not safe to deallocate those strings until all interpreters that
292
292
potentially use them are freed. By storing them in the main interpreter, we
293
293
ensure they get freed after all other interpreters are freed.
294
+
295
+ Subtle detail: it's only required to share the interned string dict in the
296
+ case that those kinds of legacy modules are actually imported. However, we
297
+ can't wait until the import happens so we share if those kind of modules are
298
+ allowed (the Py_RTFLAGS_MULTI_INTERP_EXTENSIONS flag is set).
294
299
*/
295
300
static bool
296
301
has_shared_intern_dict (PyInterpreterState * interp )
297
302
{
298
303
PyInterpreterState * main_interp = _PyInterpreterState_Main ();
299
- return interp != main_interp && interp -> feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC ;
304
+ return (interp != main_interp &&
305
+ !(interp -> feature_flags & Py_RTFLAGS_MULTI_INTERP_EXTENSIONS ));
300
306
}
301
307
302
308
static int
@@ -305,8 +311,20 @@ init_interned_dict(PyInterpreterState *interp)
305
311
assert (get_interned_dict (interp ) == NULL );
306
312
PyObject * interned ;
307
313
if (has_shared_intern_dict (interp )) {
308
- interned = get_interned_dict (_PyInterpreterState_Main ());
309
- Py_INCREF (interned );
314
+ PyInterpreterState * main = _PyInterpreterState_Main ();
315
+ interned = _Py_INTERP_CACHED_OBJECT (main , interned_strings_legacy );
316
+ if (interned == NULL ) {
317
+ // allocate for main interpreter. We share obmalloc in this case
318
+ // and we use a separate dict because it's cleaner to ensure these
319
+ // objects don't show up in the main interpreter (which they could
320
+ // if uswe use interned_strings). They will be shared by all
321
+ // subinterpreters that allow legacy single-phase init modules.
322
+ interned = PyDict_New ();
323
+ if (interned == NULL ) {
324
+ return -1 ;
325
+ }
326
+ _Py_INTERP_CACHED_OBJECT (main , interned_strings_legacy ) = interned ;
327
+ }
310
328
}
311
329
else {
312
330
interned = PyDict_New ();
@@ -318,6 +336,61 @@ init_interned_dict(PyInterpreterState *interp)
318
336
return 0 ;
319
337
}
320
338
339
+ /* Clean the dict of interned strings that is used by subinterpreters that
340
+ * allow basic single-phase extensions modules (has_shared_intern_dict() is
341
+ * true). For those, they all share the interned_strings_legacy dict that's
342
+ * owned by the main interpreter. Only the main interpreter does cleanup on
343
+ * it. See GH-116510.
344
+ */
345
+ static void
346
+ clear_interned_dict_legacy (PyInterpreterState * interp )
347
+ {
348
+ PyObject * interned = _Py_INTERP_CACHED_OBJECT (interp ,
349
+ interned_strings_legacy );
350
+ if (interned == NULL ) {
351
+ return ;
352
+ }
353
+ // This is similar but slightly different logic compared with
354
+ // _PyUnicode_ClearInterned(). These are strings created by
355
+ // subinterpreters but stored in a dict owned by the main interpreter.
356
+ // Immortalization loses the true reference count and so we need to ensure
357
+ // all those subinterpreters have exited before cleaning these strings up.
358
+ Py_ssize_t pos = 0 ;
359
+ PyObject * s , * ignored_value ;
360
+ while (PyDict_Next (interned , & pos , & s , & ignored_value )) {
361
+ assert (PyUnicode_IS_READY (s ));
362
+ #ifdef Py_TRACE_REFS
363
+ _Py_AddToAllObjects (s );
364
+ #endif
365
+ switch (PyUnicode_CHECK_INTERNED (s )) {
366
+ case SSTATE_INTERNED_IMMORTAL :
367
+ case SSTATE_INTERNED_IMMORTAL_STATIC :
368
+ _Py_SetMortal (s , 2 );
369
+ #ifdef Py_REF_DEBUG
370
+ /* let's be pedantic with the ref total */
371
+ _Py_IncRefTotal (_PyThreadState_GET ());
372
+ _Py_IncRefTotal (_PyThreadState_GET ());
373
+ #endif
374
+ break ;
375
+ case SSTATE_INTERNED_MORTAL :
376
+ Py_SET_REFCNT (s , Py_REFCNT (s ) + 2 );
377
+ #ifdef Py_REF_DEBUG
378
+ /* let's be pedantic with the ref total */
379
+ _Py_IncRefTotal (_PyThreadState_GET ());
380
+ _Py_IncRefTotal (_PyThreadState_GET ());
381
+ #endif
382
+ break ;
383
+ case SSTATE_NOT_INTERNED :
384
+ /* fall through */
385
+ default :
386
+ Py_UNREACHABLE ();
387
+ }
388
+ _PyUnicode_STATE (s ).interned = SSTATE_NOT_INTERNED ;
389
+ }
390
+ PyDict_Clear (interned );
391
+ _Py_INTERP_CACHED_OBJECT (interp , interned_strings_legacy ) = NULL ;
392
+ }
393
+
321
394
static void
322
395
clear_interned_dict (PyInterpreterState * interp )
323
396
{
@@ -326,8 +399,9 @@ clear_interned_dict(PyInterpreterState *interp)
326
399
if (!has_shared_intern_dict (interp )) {
327
400
// only clear if the dict belongs to this interpreter
328
401
PyDict_Clear (interned );
402
+ Py_DECREF (interned );
403
+ clear_interned_dict_legacy (interp );
329
404
}
330
- Py_DECREF (interned );
331
405
_Py_INTERP_CACHED_OBJECT (interp , interned_strings ) = NULL ;
332
406
}
333
407
}
0 commit comments