Skip to content

Commit b6b9b7a

Browse files
committed
Ensure objects found by gc.get_objects() are kept alive.
After the `_PyEval_StartTheWorld()` call, other threads may be running and mutating objects. Ensure that the objects are kept alive by incref'ing them when they're added to the `_PyObjectStack`.
1 parent aa21b07 commit b6b9b7a

File tree

3 files changed

+105
-40
lines changed

3 files changed

+105
-40
lines changed

Include/internal/pycore_object_stack.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ _PyObjectStack_Pop(_PyObjectStack *stack)
7171
return obj;
7272
}
7373

74+
static inline Py_ssize_t
75+
_PyObjectStack_Size(_PyObjectStack *stack)
76+
{
77+
Py_ssize_t size = 0;
78+
for (_PyObjectStackChunk *buf = stack->head; buf != NULL; buf = buf->prev) {
79+
size += buf->n;
80+
}
81+
return size;
82+
}
83+
7484
// Merge src into dst, leaving src empty
7585
extern void
7686
_PyObjectStack_Merge(_PyObjectStack *dst, _PyObjectStack *src);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import unittest
2+
3+
import threading
4+
from threading import Thread
5+
from unittest import TestCase
6+
import gc
7+
8+
from test.support import threading_helper
9+
10+
11+
class MyObj:
12+
pass
13+
14+
15+
@threading_helper.requires_working_threading()
16+
class TestGC(TestCase):
17+
def test_get_objects(self):
18+
event = threading.Event()
19+
20+
def gc_thread():
21+
for i in range(100):
22+
o = gc.get_objects()
23+
event.set()
24+
25+
def mutator_thread():
26+
while not event.is_set():
27+
o1 = MyObj()
28+
o2 = MyObj()
29+
o3 = MyObj()
30+
o4 = MyObj()
31+
32+
gcs = [Thread(target=gc_thread)]
33+
mutators = [Thread(target=mutator_thread) for _ in range(4)]
34+
with threading_helper.start_threads(gcs + mutators):
35+
pass
36+
37+
def test_get_referrers(self):
38+
event = threading.Event()
39+
40+
obj = MyObj()
41+
42+
def gc_thread():
43+
for i in range(100):
44+
o = gc.get_referrers(obj)
45+
event.set()
46+
47+
def mutator_thread():
48+
while not event.is_set():
49+
d1 = { "key": obj }
50+
d2 = { "key": obj }
51+
d3 = { "key": obj }
52+
d4 = { "key": obj }
53+
54+
gcs = [Thread(target=gc_thread) for _ in range(2)]
55+
mutators = [Thread(target=mutator_thread) for _ in range(4)]
56+
with threading_helper.start_threads(gcs + mutators):
57+
pass
58+
59+
60+
if __name__ == "__main__":
61+
unittest.main()

Python/gc_free_threading.c

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,28 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
14011401
return n + m;
14021402
}
14031403

1404+
static PyObject *
1405+
list_from_object_stack(_PyObjectStack *stack)
1406+
{
1407+
PyObject *list = PyList_New(_PyObjectStack_Size(stack));
1408+
if (list == NULL) {
1409+
PyObject *op;
1410+
while ((op = _PyObjectStack_Pop(stack)) != NULL) {
1411+
Py_DECREF(op);
1412+
}
1413+
return NULL;
1414+
}
1415+
1416+
PyObject *op;
1417+
Py_ssize_t idx = 0;
1418+
while ((op = _PyObjectStack_Pop(stack)) != NULL) {
1419+
assert(idx < PyList_GET_SIZE(list));
1420+
PyList_SET_ITEM(list, idx++, op);
1421+
}
1422+
assert(idx == PyList_GET_SIZE(list));
1423+
return list;
1424+
}
1425+
14041426
struct get_referrers_args {
14051427
struct visitor_args base;
14061428
PyObject *objs;
@@ -1435,8 +1457,12 @@ visit_get_referrers(const mi_heap_t *heap, const mi_heap_area_t *area,
14351457
}
14361458

14371459
struct get_referrers_args *arg = (struct get_referrers_args *)args;
1460+
if (op == arg->objs) {
1461+
// Don't include the tuple itself in the referrers list.
1462+
return true;
1463+
}
14381464
if (Py_TYPE(op)->tp_traverse(op, referrersvisit, arg->objs)) {
1439-
if (_PyObjectStack_Push(&arg->results, op) < 0) {
1465+
if (_PyObjectStack_Push(&arg->results, Py_NewRef(op)) < 0) {
14401466
return false;
14411467
}
14421468
}
@@ -1447,11 +1473,6 @@ visit_get_referrers(const mi_heap_t *heap, const mi_heap_area_t *area,
14471473
PyObject *
14481474
_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs)
14491475
{
1450-
PyObject *result = PyList_New(0);
1451-
if (!result) {
1452-
return NULL;
1453-
}
1454-
14551476
// NOTE: We can't append to the PyListObject during gc_visit_heaps()
14561477
// because PyList_Append() may reclaim an abandoned mimalloc segments
14571478
// while we are traversing them.
@@ -1460,23 +1481,12 @@ _PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs)
14601481
int err = gc_visit_heaps(interp, &visit_get_referrers, &args.base);
14611482
_PyEval_StartTheWorld(interp);
14621483

1484+
PyObject *list = list_from_object_stack(&args.results);
14631485
if (err < 0) {
14641486
PyErr_NoMemory();
1465-
goto error;
1487+
Py_CLEAR(list);
14661488
}
1467-
1468-
PyObject *op;
1469-
while ((op = _PyObjectStack_Pop(&args.results)) != NULL) {
1470-
if (op != objs && PyList_Append(result, op) < 0) {
1471-
goto error;
1472-
}
1473-
}
1474-
return result;
1475-
1476-
error:
1477-
Py_DECREF(result);
1478-
_PyObjectStack_Clear(&args.results);
1479-
return NULL;
1489+
return list;
14801490
}
14811491

14821492
struct get_objects_args {
@@ -1499,7 +1509,7 @@ visit_get_objects(const mi_heap_t *heap, const mi_heap_area_t *area,
14991509
}
15001510

15011511
struct get_objects_args *arg = (struct get_objects_args *)args;
1502-
if (_PyObjectStack_Push(&arg->objects, op) < 0) {
1512+
if (_PyObjectStack_Push(&arg->objects, Py_NewRef(op)) < 0) {
15031513
return false;
15041514
}
15051515
return true;
@@ -1508,11 +1518,6 @@ visit_get_objects(const mi_heap_t *heap, const mi_heap_area_t *area,
15081518
PyObject *
15091519
_PyGC_GetObjects(PyInterpreterState *interp, int generation)
15101520
{
1511-
PyObject *result = PyList_New(0);
1512-
if (!result) {
1513-
return NULL;
1514-
}
1515-
15161521
// NOTE: We can't append to the PyListObject during gc_visit_heaps()
15171522
// because PyList_Append() may reclaim an abandoned mimalloc segments
15181523
// while we are traversing them.
@@ -1521,23 +1526,12 @@ _PyGC_GetObjects(PyInterpreterState *interp, int generation)
15211526
int err = gc_visit_heaps(interp, &visit_get_objects, &args.base);
15221527
_PyEval_StartTheWorld(interp);
15231528

1529+
PyObject *list = list_from_object_stack(&args.objects);
15241530
if (err < 0) {
15251531
PyErr_NoMemory();
1526-
goto error;
1532+
Py_CLEAR(list);
15271533
}
1528-
1529-
PyObject *op;
1530-
while ((op = _PyObjectStack_Pop(&args.objects)) != NULL) {
1531-
if (op != result && PyList_Append(result, op) < 0) {
1532-
goto error;
1533-
}
1534-
}
1535-
return result;
1536-
1537-
error:
1538-
Py_DECREF(result);
1539-
_PyObjectStack_Clear(&args.objects);
1540-
return NULL;
1534+
return list;
15411535
}
15421536

15431537
static bool

0 commit comments

Comments
 (0)