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]

Reply via email to