@@ -14188,8 +14188,11 @@ immortalize_interned(PyObject *s)
1418814188 _Py_DecRefTotal (_PyThreadState_GET ());
1418914189 }
1419014190#endif
14191- FT_ATOMIC_STORE_UINT8_RELAXED (_PyUnicode_STATE (s ).interned , SSTATE_INTERNED_IMMORTAL );
1419214191 _Py_SetImmortal (s );
14192+ // The switch to SSTATE_INTERNED_IMMORTAL must be the last thing done here
14193+ // to synchronize with the check in intern_common() that avoids locking if
14194+ // the string is already immortal.
14195+ FT_ATOMIC_STORE_UINT8 (_PyUnicode_STATE (s ).interned , SSTATE_INTERNED_IMMORTAL );
1419314196}
1419414197
1419514198static /* non-null */ PyObject *
@@ -14271,6 +14274,23 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */,
1427114274 assert (interned != NULL );
1427214275#ifdef Py_GIL_DISABLED
1427314276# define INTERN_MUTEX &_Py_INTERP_CACHED_OBJECT(interp, interned_mutex)
14277+ // Lock-free fast path: check if there's already an interned copy that
14278+ // is in its final immortal state.
14279+ PyObject * r ;
14280+ int res = PyDict_GetItemRef (interned , s , & r );
14281+ if (res < 0 ) {
14282+ PyErr_Clear ();
14283+ return s ;
14284+ }
14285+ if (res > 0 ) {
14286+ unsigned int state = _Py_atomic_load_uint8 (& _PyUnicode_STATE (r ).interned );
14287+ if (state == SSTATE_INTERNED_IMMORTAL ) {
14288+ Py_DECREF (s );
14289+ return r ;
14290+ }
14291+ // Not yet fully interned; fall through to the locking path.
14292+ Py_DECREF (r );
14293+ }
1427414294#endif
1427514295 FT_MUTEX_LOCK (INTERN_MUTEX );
1427614296 PyObject * t ;
@@ -14308,7 +14328,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */,
1430814328 Py_DECREF (s );
1430914329 Py_DECREF (s );
1431014330 }
14311- FT_ATOMIC_STORE_UINT8_RELAXED (_PyUnicode_STATE (s ).interned , SSTATE_INTERNED_MORTAL );
14331+ FT_ATOMIC_STORE_UINT8 (_PyUnicode_STATE (s ).interned , SSTATE_INTERNED_MORTAL );
1431214332
1431314333 /* INTERNED_MORTAL -> INTERNED_IMMORTAL (if needed) */
1431414334
0 commit comments