diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst new file mode 100644 index 00000000000000..985d4264befae2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst @@ -0,0 +1,2 @@ +Fix a crash in free-threaded builds when :func:`codecs.unregister` is +called concurrently. diff --git a/Python/codecs.c b/Python/codecs.c index c881d82e30307a..b78d51858f64e5 100644 --- a/Python/codecs.c +++ b/Python/codecs.c @@ -63,24 +63,21 @@ PyCodec_Unregister(PyObject *search_function) PyObject *codec_search_path = interp->codecs.search_path; assert(PyList_CheckExact(codec_search_path)); + FT_MUTEX_LOCK(&interp->codecs.search_path_mutex); for (Py_ssize_t i = 0; i < PyList_GET_SIZE(codec_search_path); i++) { - FT_MUTEX_LOCK(&interp->codecs.search_path_mutex); - PyObject *item = PyList_GetItemRef(codec_search_path, i); - int ret = 1; - if (item == search_function) { - // We hold a reference to the item, so its destructor can't run - // while we hold search_path_mutex. - ret = PyList_SetSlice(codec_search_path, i, i+1, NULL); - } - FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex); - Py_DECREF(item); - if (ret != 1) { + if (PyList_GET_ITEM(codec_search_path, i) == search_function) { + // Keep the function alive until after releasing the mutex. + Py_INCREF(search_function); + int ret = PyList_SetSlice(codec_search_path, i, i+1, NULL); + FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex); + Py_DECREF(search_function); assert(interp->codecs.search_cache != NULL); assert(PyDict_CheckExact(interp->codecs.search_cache)); PyDict_Clear(interp->codecs.search_cache); return ret; } } + FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex); return 0; }