Skip to content

Commit 47667b8

Browse files
[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 784b305) Co-authored-by: Jelle Zijlstra <jelle@openai.com>
1 parent 1526be6 commit 47667b8

2 files changed

Lines changed: 10 additions & 11 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in free-threaded builds when :func:`codecs.unregister` is
2+
called concurrently.

‎Python/codecs.c‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,21 @@ PyCodec_Unregister(PyObject *search_function)
6363

6464
PyObject *codec_search_path = interp->codecs.search_path;
6565
assert(PyList_CheckExact(codec_search_path));
66+
FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
6667
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(codec_search_path); i++) {
67-
FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
68-
PyObject *item = PyList_GetItemRef(codec_search_path, i);
69-
int ret = 1;
70-
if (item == search_function) {
71-
// We hold a reference to the item, so its destructor can't run
72-
// while we hold search_path_mutex.
73-
ret = PyList_SetSlice(codec_search_path, i, i+1, NULL);
74-
}
75-
FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
76-
Py_DECREF(item);
77-
if (ret != 1) {
68+
if (PyList_GET_ITEM(codec_search_path, i) == search_function) {
69+
// Keep the function alive until after releasing the mutex.
70+
Py_INCREF(search_function);
71+
int ret = PyList_SetSlice(codec_search_path, i, i+1, NULL);
72+
FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
73+
Py_DECREF(search_function);
7874
assert(interp->codecs.search_cache != NULL);
7975
assert(PyDict_CheckExact(interp->codecs.search_cache));
8076
PyDict_Clear(interp->codecs.search_cache);
8177
return ret;
8278
}
8379
}
80+
FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
8481
return 0;
8582
}
8683

0 commit comments

Comments
 (0)