https://github.com/python/cpython/commit/784b305df8821d57fb6fb6c52794ef67bda0a75d
commit: 784b305df8821d57fb6fb6c52794ef67bda0a75d
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-10-22T18:24:53Z
summary:
gh-159739: Hold the codec registry lock while unregistering (#160168)
Concurrent calls to codecs.unregister() can shrink the search path between its
length check and indexed lookup in a free-threaded build. The lookup then
returns NULL and an unconditional decref crashes the interpreter; hold the
registry mutex across the scan and removal. Also reported in gh-157078.
Backport to 3.15 and 3.14.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-19-55-00.gh-issue-159739.codc01.rst
M Python/codecs.c
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 2621a17daf1814..ba6f618f77e6f6 100644
--- a/Python/codecs.c
+++ b/Python/codecs.c
@@ -64,24 +64,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;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]