Skip to content

Commit 412bc2a

Browse files
committed
Reduce code duplication.
Add a boolean flag to indicate that callbacks should be enabled. This allows the same function to be used before and after finalizer execution.
1 parent 6a1b24b commit 412bc2a

File tree

2 files changed

+24
-101
lines changed

2 files changed

+24
-101
lines changed

Python/gc.c

Lines changed: 16 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ move_legacy_finalizer_reachable(PyGC_Head *finalizers)
870870
* no object in `unreachable` is weakly referenced anymore.
871871
*/
872872
static int
873-
handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
873+
handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old, bool allow_callbacks)
874874
{
875875
PyGC_Head *gc;
876876
PyObject *op; /* generally FROM_GC(gc) */
@@ -879,7 +879,9 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
879879
PyGC_Head *next;
880880
int num_freed = 0;
881881

882-
gc_list_init(&wrcb_to_call);
882+
if (allow_callbacks) {
883+
gc_list_init(&wrcb_to_call);
884+
}
883885

884886
/* Clear all weakrefs to the objects in unreachable. If such a weakref
885887
* also has a callback, move it into `wrcb_to_call` if the callback
@@ -935,6 +937,11 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
935937
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == op);
936938
_PyWeakref_ClearRef(wr);
937939
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None);
940+
941+
if (!allow_callbacks) {
942+
continue;
943+
}
944+
938945
if (wr->wr_callback == NULL) {
939946
/* no callback */
940947
continue;
@@ -987,6 +994,10 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
987994
}
988995
}
989996

997+
if (!allow_callbacks) {
998+
return 0;
999+
}
1000+
9901001
/* Invoke the callbacks we decided to honor. It's safe to invoke them
9911002
* because they can't reference unreachable objects.
9921003
*/
@@ -1037,62 +1048,6 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
10371048
return num_freed;
10381049
}
10391050

1040-
/* Clear all weakrefs to unreachable objects. When this returns, no object in
1041-
* `unreachable` is weakly referenced anymore.
1042-
*/
1043-
static void
1044-
clear_weakrefs(PyGC_Head *unreachable)
1045-
{
1046-
PyGC_Head *gc;
1047-
PyGC_Head *next;
1048-
1049-
for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
1050-
PyWeakReference **wrlist;
1051-
1052-
PyObject *op = FROM_GC(gc);
1053-
next = GC_NEXT(gc);
1054-
1055-
if (PyWeakref_Check(op)) {
1056-
/* A weakref inside the unreachable set must be cleared. If we
1057-
* allow its callback to execute inside delete_garbage(), it
1058-
* could expose objects that have tp_clear already called on
1059-
* them. Or, it could resurrect unreachable objects. One way
1060-
* this can happen is if some container objects do not implement
1061-
* tp_traverse. Then, wr_object can be outside the unreachable
1062-
* set but can be deallocated as a result of breaking the
1063-
* reference cycle. If we don't clear the weakref, the callback
1064-
* will run and potentially cause a crash. See bpo-38006 for
1065-
* one example.
1066-
*/
1067-
_PyWeakref_ClearRef((PyWeakReference *)op);
1068-
}
1069-
1070-
if (! _PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) {
1071-
continue;
1072-
}
1073-
1074-
/* It supports weakrefs. Does it have any?
1075-
*
1076-
* This is never triggered for static types so we can avoid the
1077-
* (slightly) more costly _PyObject_GET_WEAKREFS_LISTPTR().
1078-
*/
1079-
wrlist = _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(op);
1080-
1081-
/* `op` may have some weakrefs. March over the list, clear
1082-
* all the weakrefs.
1083-
*/
1084-
for (PyWeakReference *wr = *wrlist; wr != NULL; wr = *wrlist) {
1085-
/* _PyWeakref_ClearRef clears the weakref but leaves
1086-
* the callback pointer intact. Obscure: it also
1087-
* changes *wrlist.
1088-
*/
1089-
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == op);
1090-
_PyWeakref_ClearRef(wr);
1091-
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None);
1092-
}
1093-
}
1094-
}
1095-
10961051
static void
10971052
debug_cycle(const char *msg, PyObject *op)
10981053
{
@@ -1793,7 +1748,7 @@ gc_collect_region(PyThreadState *tstate,
17931748
}
17941749

17951750
/* Clear weakrefs and invoke callbacks as necessary. */
1796-
stats->collected += handle_weakrefs(&unreachable, to);
1751+
stats->collected += handle_weakrefs(&unreachable, to, true);
17971752
gc_list_validate_space(to, gcstate->visited_space);
17981753
validate_list(to, collecting_clear_unreachable_clear);
17991754
validate_list(&unreachable, collecting_set_unreachable_clear);
@@ -1811,9 +1766,9 @@ gc_collect_region(PyThreadState *tstate,
18111766
* code must be allowed to access those unreachable objects. During
18121767
* delete_garbage(), finalizers outside the unreachable set might run
18131768
* and create new weakrefs. If those weakrefs were not cleared, they
1814-
* could reveal unreachable objects.
1769+
* could reveal unreachable objects. Callbacks are not executed.
18151770
*/
1816-
clear_weakrefs(&final_unreachable);
1771+
handle_weakrefs(&final_unreachable, NULL, false);
18171772

18181773
/* Call tp_clear on objects in the final_unreachable set. This will cause
18191774
* the reference cycles to be broken. It may also cause some objects

Python/gc_free_threading.c

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,9 +1492,9 @@ move_legacy_finalizer_reachable(struct collection_state *state)
14921492
}
14931493

14941494
// Clear all weakrefs to unreachable objects. Weakrefs with callbacks are
1495-
// enqueued in `wrcb_to_call`, but not invoked yet.
1495+
// optionally enqueued in `wrcb_to_call`, but not invoked yet.
14961496
static void
1497-
handle_weakrefs(struct collection_state *state)
1497+
clear_weakrefs(struct collection_state *state, bool enqueue_callbacks)
14981498
{
14991499
PyObject *op;
15001500
WORKSTACK_FOR_EACH(&state->unreachable, op) {
@@ -1526,6 +1526,10 @@ handle_weakrefs(struct collection_state *state)
15261526
_PyWeakref_ClearRef(wr);
15271527
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None);
15281528

1529+
if (!enqueue_callbacks) {
1530+
continue;
1531+
}
1532+
15291533
// We do not invoke callbacks for weakrefs that are themselves
15301534
// unreachable. This is partly for historical reasons: weakrefs
15311535
// predate safe object finalization, and a weakref that is itself
@@ -1545,42 +1549,6 @@ handle_weakrefs(struct collection_state *state)
15451549
}
15461550
}
15471551

1548-
// Clear all weakrefs to unreachable objects.
1549-
static void
1550-
clear_weakrefs(struct collection_state *state)
1551-
{
1552-
PyObject *op;
1553-
WORKSTACK_FOR_EACH(&state->unreachable, op) {
1554-
if (PyWeakref_Check(op)) {
1555-
// Clear weakrefs that are themselves unreachable to ensure their
1556-
// callbacks will not be executed later from a `tp_clear()`
1557-
// inside delete_garbage(). That would be unsafe: it could
1558-
// resurrect a dead object or access a an already cleared object.
1559-
// See bpo-38006 for one example.
1560-
_PyWeakref_ClearRef((PyWeakReference *)op);
1561-
}
1562-
1563-
if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) {
1564-
continue;
1565-
}
1566-
1567-
// NOTE: This is never triggered for static types so we can avoid the
1568-
// (slightly) more costly _PyObject_GET_WEAKREFS_LISTPTR().
1569-
PyWeakReference **wrlist = _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(op);
1570-
1571-
// `op` may have some weakrefs. March over the list, clear
1572-
// all the weakrefs.
1573-
for (PyWeakReference *wr = *wrlist; wr != NULL; wr = *wrlist) {
1574-
// _PyWeakref_ClearRef clears the weakref but leaves
1575-
// the callback pointer intact. Obscure: it also
1576-
// changes *wrlist.
1577-
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == op);
1578-
_PyWeakref_ClearRef(wr);
1579-
_PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None);
1580-
}
1581-
}
1582-
}
1583-
15841552
static void
15851553
call_weakref_callbacks(struct collection_state *state)
15861554
{
@@ -2247,7 +2215,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
22472215
interp->gc.long_lived_total = state->long_lived_total;
22482216

22492217
// Clear weakrefs and enqueue callbacks (but do not call them).
2250-
handle_weakrefs(state);
2218+
clear_weakrefs(state, true);
22512219
_PyEval_StartTheWorld(interp);
22522220

22532221
// Deallocate any object from the refcount merge step
@@ -2269,7 +2237,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
22692237
// delete_garbage(), finalizers outside the unreachable set might
22702238
// run and create new weakrefs. If those weakrefs were not cleared,
22712239
// they could reveal unreachable objects.
2272-
clear_weakrefs(state);
2240+
clear_weakrefs(state, false);
22732241
}
22742242
_PyEval_StartTheWorld(interp);
22752243

0 commit comments

Comments
 (0)