Skip to content

Commit ecda3ae

Browse files
[3.13] pythongh-126312: Don't traverse frozen objects on the free-threaded build (pythonGH-126338) (python#126866)
* Fix merge conflicts. * [3.13] pythongh-126312: Don't traverse frozen objects on the free-threaded build (pythonGH-126338) Also, _PyGC_Freeze() no longer freezes unreachable objects. (cherry picked from commit d4c72fe) Co-authored-by: Peter Bierma <[email protected]> Co-authored-by: Sergey B Kirpichev <[email protected]> --------- Co-authored-by: Sergey B Kirpichev <[email protected]>
1 parent 3227680 commit ecda3ae

File tree

3 files changed

+54
-4
lines changed

3 files changed

+54
-4
lines changed

Lib/test/test_gc.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,45 @@ def __del__(self):
11291129
gc.collect()
11301130
self.assertTrue(collected)
11311131

1132+
def test_traverse_frozen_objects(self):
1133+
# See GH-126312: Objects that were not frozen could traverse over
1134+
# a frozen object on the free-threaded build, which would cause
1135+
# a negative reference count.
1136+
x = [1, 2, 3]
1137+
gc.freeze()
1138+
y = [x]
1139+
y.append(y)
1140+
del y
1141+
gc.collect()
1142+
gc.unfreeze()
1143+
1144+
def test_deferred_refcount_frozen(self):
1145+
# Also from GH-126312: objects that use deferred reference counting
1146+
# weren't ignored if they were frozen. Unfortunately, it's pretty
1147+
# difficult to come up with a case that triggers this.
1148+
#
1149+
# Calling gc.collect() while the garbage collector is frozen doesn't
1150+
# trigger this normally, but it *does* if it's inside unittest for whatever
1151+
# reason. We can't call unittest from inside a test, so it has to be
1152+
# in a subprocess.
1153+
source = textwrap.dedent("""
1154+
import gc
1155+
import unittest
1156+
1157+
1158+
class Test(unittest.TestCase):
1159+
def test_something(self):
1160+
gc.freeze()
1161+
gc.collect()
1162+
gc.unfreeze()
1163+
1164+
1165+
if __name__ == "__main__":
1166+
unittest.main()
1167+
""")
1168+
assert_python_ok("-c", source)
1169+
1170+
11321171
class GCCallbackTests(unittest.TestCase):
11331172
def setUp(self):
11341173
# Save gc state and disable it.
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: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ worklist_remove(struct worklist_iter *iter)
110110
iter->next = iter->ptr;
111111
}
112112

113+
static inline int
114+
gc_is_frozen(PyObject *op)
115+
{
116+
return (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0;
117+
}
118+
113119
static inline int
114120
gc_is_unreachable(PyObject *op)
115121
{
@@ -229,7 +235,7 @@ op_from_block(void *block, void *arg, bool include_frozen)
229235
if (!_PyObject_GC_IS_TRACKED(op)) {
230236
return NULL;
231237
}
232-
if (!include_frozen && (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0) {
238+
if (!include_frozen && gc_is_frozen(op)) {
233239
return NULL;
234240
}
235241
return op;
@@ -364,7 +370,10 @@ process_delayed_frees(PyInterpreterState *interp)
364370
static int
365371
visit_decref(PyObject *op, void *arg)
366372
{
367-
if (_PyObject_GC_IS_TRACKED(op) && !_Py_IsImmortal(op)) {
373+
if (_PyObject_GC_IS_TRACKED(op)
374+
&& !_Py_IsImmortal(op)
375+
&& !gc_is_frozen(op))
376+
{
368377
// If update_refs hasn't reached this object yet, mark it
369378
// as (tentatively) unreachable and initialize ob_tid to zero.
370379
gc_maybe_init_refs(op);
@@ -1419,7 +1428,7 @@ visit_freeze(const mi_heap_t *heap, const mi_heap_area_t *area,
14191428
void *block, size_t block_size, void *args)
14201429
{
14211430
PyObject *op = op_from_block(block, args, true);
1422-
if (op != NULL) {
1431+
if (op != NULL && !gc_is_unreachable(op)) {
14231432
op->ob_gc_bits |= _PyGC_BITS_FROZEN;
14241433
}
14251434
return true;
@@ -1464,7 +1473,7 @@ visit_count_frozen(const mi_heap_t *heap, const mi_heap_area_t *area,
14641473
void *block, size_t block_size, void *args)
14651474
{
14661475
PyObject *op = op_from_block(block, args, true);
1467-
if (op != NULL && (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0) {
1476+
if (op != NULL && gc_is_frozen(op)) {
14681477
struct count_frozen_args *arg = (struct count_frozen_args *)args;
14691478
arg->count++;
14701479
}

0 commit comments

Comments
 (0)