https://github.com/python/cpython/commit/72caed7b1f7d041df2c4d0d1c0a17d505164fc1b
commit: 72caed7b1f7d041df2c4d0d1c0a17d505164fc1b
branch: 3.14
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-10-24T13:43:43-07:00
summary:
[3.14] gh-159739: Hold the codec registry lock while unregistering (GH-160168)
(#160485)
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.
(cherry picked from commit 784b305df8821d57fb6fb6c52794ef67bda0a75d)
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 a8de8688b1da9e..52d3ed672ce748 100644
--- a/Python/codecs.c
+++ b/Python/codecs.c
@@ -66,28 +66,27 @@ PyCodec_Unregister(PyObject *search_function)
PyObject *codec_search_path = interp->codecs.search_path;
assert(PyList_CheckExact(codec_search_path));
- for (Py_ssize_t i = 0; i < PyList_GET_SIZE(codec_search_path); i++) {
#ifdef Py_GIL_DISABLED
- PyMutex_Lock(&interp->codecs.search_path_mutex);
+ PyMutex_Lock(&interp->codecs.search_path_mutex);
#endif
- 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);
- }
+ for (Py_ssize_t i = 0; i < PyList_GET_SIZE(codec_search_path); i++) {
+ 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);
#ifdef Py_GIL_DISABLED
- PyMutex_Unlock(&interp->codecs.search_path_mutex);
+ PyMutex_Unlock(&interp->codecs.search_path_mutex);
#endif
- Py_DECREF(item);
- if (ret != 1) {
+ 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;
}
}
+#ifdef Py_GIL_DISABLED
+ PyMutex_Unlock(&interp->codecs.search_path_mutex);
+#endif
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]