Skip to content

Commit d8d3df5

Browse files
encukoumiss-islingtondzaramelcone
authored
[3.14] gh-149816: #96 Fix a race condition in invoke_gc_callback with free threading (GH-150029) (GH-156119) (#159310)
(cherry picked from commit 91d71dd) (cherry picked from commit e96bd13) Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Co-authored-by: dzaramelcone <134235821+dzaramelcone@users.noreply.github.com>
1 parent 6cdb03d commit d8d3df5

3 files changed

Lines changed: 43 additions & 15 deletions

File tree

‎Lib/test/test_free_threading/test_gc.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import threading
44
from threading import Thread
5+
import time
56
from unittest import TestCase
67
import gc
78
import weakref
@@ -96,6 +97,27 @@ def evil():
9697
thread.start()
9798
thread.join()
9899

100+
def test_gc_callbacks_race_with_mutation(self):
101+
def collect():
102+
b.wait()
103+
while not stop.is_set():
104+
gc.collect()
105+
106+
def mutate():
107+
b.wait()
108+
while not stop.is_set():
109+
gc.callbacks[:] = [lambda *_: _ for _ in range(16)]
110+
time.sleep(0)
111+
gc.callbacks.clear()
112+
113+
threads = [threading.Thread(target=f) for f in (collect, mutate) * 4]
114+
b = threading.Barrier(len(threads) + 1)
115+
stop = threading.Event()
116+
117+
with threading_helper.start_threads(threads, stop.set):
118+
b.wait()
119+
time.sleep(0.2)
120+
99121
def test_merge_brc_queue_of_detached_thread(self):
100122
# GH-157838: objects queued for merging by a thread that is detached
101123
# (blocked in a lock acquire, sleep, etc.) are merged and freed on its
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix race conditions in ``invoke_gc_callback`` iterating ``gc.callbacks``
2+
in free-threading mode.

‎Python/gc_free_threading.c‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY()
1010
#include "pycore_interp.h" // PyInterpreterState.gc
1111
#include "pycore_interpframe.h" // _PyFrame_GetLocalsArray()
12+
#include "pycore_list.h" // _PyList_GetItemRef()
1213
#include "pycore_object_alloc.h" // _PyObject_MallocWithType()
1314
#include "pycore_pystate.h" // _PyThreadState_GET()
1415
#include "pycore_tstate.h" // _PyThreadStateImpl
@@ -1885,31 +1886,34 @@ invoke_gc_callback(PyThreadState *tstate, const char *phase,
18851886

18861887
/* The local variable cannot be rebound, check it for sanity */
18871888
assert(PyList_CheckExact(gcstate->callbacks));
1888-
PyObject *info = NULL;
1889-
if (PyList_GET_SIZE(gcstate->callbacks) != 0) {
1890-
info = Py_BuildValue("{sisnsn}",
1891-
"generation", generation,
1892-
"collected", collected,
1893-
"uncollectable", uncollectable);
1894-
if (info == NULL) {
1895-
PyErr_FormatUnraisable("Exception ignored while "
1896-
"invoking gc callbacks");
1897-
return;
1898-
}
1889+
if (PyList_GET_SIZE(gcstate->callbacks) == 0) {
1890+
return;
1891+
}
1892+
1893+
PyObject *info = Py_BuildValue("{sisnsn}",
1894+
"generation", generation,
1895+
"collected", collected,
1896+
"uncollectable", uncollectable);
1897+
if (info == NULL) {
1898+
PyErr_FormatUnraisable("Exception ignored while "
1899+
"invoking gc callbacks");
1900+
return;
18991901
}
19001902

19011903
PyObject *phase_obj = PyUnicode_FromString(phase);
19021904
if (phase_obj == NULL) {
1903-
Py_XDECREF(info);
1905+
Py_DECREF(info);
19041906
PyErr_FormatUnraisable("Exception ignored while "
19051907
"invoking gc callbacks");
19061908
return;
19071909
}
19081910

19091911
PyObject *stack[] = {phase_obj, info};
19101912
for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) {
1911-
PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i);
1912-
Py_INCREF(cb); /* make sure cb doesn't go away */
1913+
PyObject *r, *cb = _PyList_GetItemRef((PyListObject *)gcstate->callbacks, i);
1914+
if (cb == NULL) {
1915+
break;
1916+
}
19131917
r = PyObject_Vectorcall(cb, stack, 2, NULL);
19141918
if (r == NULL) {
19151919
PyErr_FormatUnraisable("Exception ignored while "
@@ -1921,7 +1925,7 @@ invoke_gc_callback(PyThreadState *tstate, const char *phase,
19211925
Py_DECREF(cb);
19221926
}
19231927
Py_DECREF(phase_obj);
1924-
Py_XDECREF(info);
1928+
Py_DECREF(info);
19251929
assert(!_PyErr_Occurred(tstate));
19261930
}
19271931

0 commit comments

Comments
 (0)