https://github.com/python/cpython/commit/47667b8d0d5c24439720266570fb12160f2f523c
commit: 47667b8d0d5c24439720266570fb12160f2f523c
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-10-22T15:26:02-07:00
summary:

[3.15] gh-159739: Hold the codec registry lock while unregistering (GH-160168) 
(#160292)



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)

Co-authored-by: Jelle Zijlstra <[email protected]>

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 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;
 }
 

_______________________________________________
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