Skip to content

Commit c30d166

Browse files
ZeroIntensityskirpichev
authored andcommitted
pythongh-126312: Don't traverse frozen objects on the free-threaded build (python#126338)
Also, _PyGC_Freeze() no longer freezes unreachable objects. Co-authored-by: Sergey B Kirpichev <[email protected]>
1 parent 1a7fa4b commit c30d166

File tree

3 files changed

+54
-5
lines changed

3 files changed

+54
-5
lines changed

Lib/test/test_gc.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,44 @@ def __del__(self):
10821082
gc.collect()
10831083
self.assertTrue(collected)
10841084

1085+
def test_traverse_frozen_objects(self):
1086+
# See GH-126312: Objects that were not frozen could traverse over
1087+
# a frozen object on the free-threaded build, which would cause
1088+
# a negative reference count.
1089+
x = [1, 2, 3]
1090+
gc.freeze()
1091+
y = [x]
1092+
y.append(y)
1093+
del y
1094+
gc.collect()
1095+
gc.unfreeze()
1096+
1097+
def test_deferred_refcount_frozen(self):
1098+
# Also from GH-126312: objects that use deferred reference counting
1099+
# weren't ignored if they were frozen. Unfortunately, it's pretty
1100+
# difficult to come up with a case that triggers this.
1101+
#
1102+
# Calling gc.collect() while the garbage collector is frozen doesn't
1103+
# trigger this normally, but it *does* if it's inside unittest for whatever
1104+
# reason. We can't call unittest from inside a test, so it has to be
1105+
# in a subprocess.
1106+
source = textwrap.dedent("""
1107+
import gc
1108+
import unittest
1109+
1110+
1111+
class Test(unittest.TestCase):
1112+
def test_something(self):
1113+
gc.freeze()
1114+
gc.collect()
1115+
gc.unfreeze()
1116+
1117+
1118+
if __name__ == "__main__":
1119+
unittest.main()
1120+
""")
1121+
assert_python_ok("-c", source)
1122+
10851123

10861124
class IncrementalGCTests(unittest.TestCase):
10871125

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash during garbage collection on an object frozen by :func:`gc.freeze` on the
2+
free-threaded build.

Python/gc_free_threading.c

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ worklist_remove(struct worklist_iter *iter)
113113
iter->next = iter->ptr;
114114
}
115115

116+
static inline int
117+
gc_is_frozen(PyObject *op)
118+
{
119+
return (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0;
120+
}
121+
116122
static inline int
117123
gc_is_unreachable(PyObject *op)
118124
{
@@ -277,7 +283,7 @@ op_from_block(void *block, void *arg, bool include_frozen)
277283
if (!_PyObject_GC_IS_TRACKED(op)) {
278284
return NULL;
279285
}
280-
if (!include_frozen && (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0) {
286+
if (!include_frozen && gc_is_frozen(op)) {
281287
return NULL;
282288
}
283289
return op;
@@ -358,7 +364,7 @@ gc_visit_stackref(_PyStackRef stackref)
358364
// being dead already.
359365
if (PyStackRef_IsDeferred(stackref) && !PyStackRef_IsNull(stackref)) {
360366
PyObject *obj = PyStackRef_AsPyObjectBorrow(stackref);
361-
if (_PyObject_GC_IS_TRACKED(obj)) {
367+
if (_PyObject_GC_IS_TRACKED(obj) && !gc_is_frozen(obj)) {
362368
gc_add_refs(obj, 1);
363369
}
364370
}
@@ -439,7 +445,10 @@ process_delayed_frees(PyInterpreterState *interp)
439445
static int
440446
visit_decref(PyObject *op, void *arg)
441447
{
442-
if (_PyObject_GC_IS_TRACKED(op) && !_Py_IsImmortal(op)) {
448+
if (_PyObject_GC_IS_TRACKED(op)
449+
&& !_Py_IsImmortal(op)
450+
&& !gc_is_frozen(op))
451+
{
443452
// If update_refs hasn't reached this object yet, mark it
444453
// as (tentatively) unreachable and initialize ob_tid to zero.
445454
gc_maybe_init_refs(op);
@@ -1539,7 +1548,7 @@ visit_freeze(const mi_heap_t *heap, const mi_heap_area_t *area,
15391548
void *block, size_t block_size, void *args)
15401549
{
15411550
PyObject *op = op_from_block(block, args, true);
1542-
if (op != NULL) {
1551+
if (op != NULL && !gc_is_unreachable(op)) {
15431552
op->ob_gc_bits |= _PyGC_BITS_FROZEN;
15441553
}
15451554
return true;
@@ -1584,7 +1593,7 @@ visit_count_frozen(const mi_heap_t *heap, const mi_heap_area_t *area,
15841593
void *block, size_t block_size, void *args)
15851594
{
15861595
PyObject *op = op_from_block(block, args, true);
1587-
if (op != NULL && (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0) {
1596+
if (op != NULL && gc_is_frozen(op)) {
15881597
struct count_frozen_args *arg = (struct count_frozen_args *)args;
15891598
arg->count++;
15901599
}

0 commit comments

Comments
 (0)