mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
gh-159137: Skip redundant GC resurrection analysis (#159138)
This commit is contained in:
1 parent
8bf6305bc7
commit
fbbf4f48c4
4 files changed
+76
-20
No files matched your search
@@ -8,6 +8,7 @@ extern "C" {
|
||||
# error "this header requires Py_BUILD_CORE define"
|
||||
#endif
|
||||
|
||||
#include "pycore_frame.h" // FRAME_STATE_FINISHED()
|
||||
#include "pycore_interpframe_structs.h" // _PyGenObject
|
||||
|
||||
#include <stddef.h> // offsetof()
|
||||
@@ -21,6 +22,14 @@ PyGenObject *_PyGen_GetGeneratorFromFrame(_PyInterpreterFrame *frame)
|
||||
return (PyGenObject *)(((char *)frame) - offset_in_gen);
|
||||
}
|
||||
|
||||
// The generator finalizer is a no-op once its frame is cleared.
|
||||
static inline int
|
||||
_PyGen_FinalizerIsNoop(PyObject *op)
|
||||
{
|
||||
return Py_TYPE(op)->tp_finalize == PyGen_Type.tp_finalize &&
|
||||
FRAME_STATE_FINISHED(((PyGenObject *)op)->gi_frame_state);
|
||||
}
|
||||
|
||||
PyAPI_FUNC(PyObject *)_PyGen_yf(PyGenObject *);
|
||||
extern int _PyGen_ClearFrame(PyGenObject *self);
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Avoid a second reachability analysis in the garbage collector when no weakref
|
||||
callback, finalizer, or collectable-object debug output ran. Finished generators,
|
||||
coroutines, and async generators can skip this analysis even when finalized.
|
||||
Free-threaded builds also avoid a second stop-the-world pause in this case.
|
||||
+27
-5
@@ -5,6 +5,7 @@
|
||||
#include "Python.h"
|
||||
#include "pycore_ceval.h" // _Py_set_eval_breaker_bit()
|
||||
#include "pycore_dict.h" // _PyInlineValuesSize()
|
||||
#include "pycore_genobject.h" // _PyGen_FinalizerIsNoop()
|
||||
#include "pycore_initconfig.h" // _PyStatus_OK()
|
||||
#include "pycore_context.h"
|
||||
#include "pycore_interp.h" // PyInterpreterState.gc
|
||||
@@ -805,7 +806,8 @@ move_legacy_finalizer_reachable(PyGC_Head *finalizers)
|
||||
* Python-level code. See bpo-38006 as an example bug.
|
||||
*/
|
||||
static int
|
||||
handle_weakref_callbacks(PyGC_Head *unreachable, PyGC_Head *old)
|
||||
handle_weakref_callbacks(PyGC_Head *unreachable, PyGC_Head *old,
|
||||
bool *needs_resurrection_check)
|
||||
{
|
||||
PyGC_Head *gc;
|
||||
PyGC_Head wrcb_to_call; /* weakrefs with callbacks to call */
|
||||
@@ -906,6 +908,9 @@ handle_weakref_callbacks(PyGC_Head *unreachable, PyGC_Head *old)
|
||||
/* Invoke the callbacks we decided to honor. It's safe to invoke them
|
||||
* because they can't reference unreachable objects.
|
||||
*/
|
||||
if (!*needs_resurrection_check) {
|
||||
*needs_resurrection_check = !gc_list_is_empty(&wrcb_to_call);
|
||||
}
|
||||
while (! gc_list_is_empty(&wrcb_to_call)) {
|
||||
PyObject *temp;
|
||||
PyObject *callback;
|
||||
@@ -1043,7 +1048,8 @@ handle_legacy_finalizers(PyThreadState *tstate,
|
||||
* list, due to refcounts falling to 0.
|
||||
*/
|
||||
static void
|
||||
finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
|
||||
finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable,
|
||||
bool *needs_resurrection_check)
|
||||
{
|
||||
destructor finalize;
|
||||
PyGC_Head seen;
|
||||
@@ -1065,6 +1071,9 @@ finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
|
||||
if (!_PyGC_FINALIZED(op) &&
|
||||
(finalize = Py_TYPE(op)->tp_finalize) != NULL)
|
||||
{
|
||||
if (!_PyGen_FinalizerIsNoop(op)) {
|
||||
*needs_resurrection_check = true;
|
||||
}
|
||||
_PyGC_SET_FINALIZED(op);
|
||||
Py_INCREF(op);
|
||||
finalize(op);
|
||||
@@ -1550,26 +1559,39 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
|
||||
validate_list(&finalizers, collecting_clear_unreachable_clear);
|
||||
validate_list(&unreachable, collecting_set_unreachable_clear);
|
||||
|
||||
bool needs_resurrection_check = false;
|
||||
|
||||
/* Print debugging information. */
|
||||
if (gcstate->debug & _PyGC_DEBUG_COLLECTABLE) {
|
||||
/* sys.stderr.write() can resurrect objects. */
|
||||
needs_resurrection_check = true;
|
||||
for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
|
||||
debug_cycle("collectable", FROM_GC(gc));
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear weakrefs and invoke callbacks as necessary. */
|
||||
stats.collected += handle_weakref_callbacks(&unreachable, old);
|
||||
stats.collected += handle_weakref_callbacks(&unreachable, old,
|
||||
&needs_resurrection_check);
|
||||
validate_list(old, collecting_clear_unreachable_clear);
|
||||
validate_list(&unreachable, collecting_set_unreachable_clear);
|
||||
|
||||
/* Call tp_finalize on objects which have one. */
|
||||
finalize_garbage(tstate, &unreachable);
|
||||
finalize_garbage(tstate, &unreachable, &needs_resurrection_check);
|
||||
|
||||
/* Handle any objects that may have resurrected after the call
|
||||
* to 'finalize_garbage' and continue the collection with the
|
||||
* objects that are still unreachable */
|
||||
PyGC_Head final_unreachable;
|
||||
handle_resurrected_objects(&unreachable, &final_unreachable, old);
|
||||
if (needs_resurrection_check) {
|
||||
handle_resurrected_objects(&unreachable, &final_unreachable, old);
|
||||
}
|
||||
else {
|
||||
/* No Python code ran during finalization, so the original
|
||||
* unreachable set is still valid. */
|
||||
gc_list_init(&final_unreachable);
|
||||
gc_list_merge(&unreachable, &final_unreachable);
|
||||
}
|
||||
|
||||
/* Clear weakrefs to objects in the unreachable set. No Python-level
|
||||
* code must be allowed to access those unreachable objects. During
|
||||
|
||||
+36
-15
@@ -1542,11 +1542,19 @@ move_legacy_finalizer_reachable(struct collection_state *state)
|
||||
// or missing `tp_traverse` methods. When that object goes away, the callback
|
||||
// for weakref can be executed and that could reveal unreachable objects to
|
||||
// Python-level code. See bpo-38006 as an example bug.
|
||||
static void
|
||||
find_weakref_callbacks(struct collection_state *state)
|
||||
static bool
|
||||
prepare_finalization(struct collection_state *state,
|
||||
bool *needs_resurrection_check)
|
||||
{
|
||||
bool has_finalizers = false;
|
||||
PyObject *op;
|
||||
WORKSTACK_FOR_EACH(&state->unreachable, op) {
|
||||
if (!_PyGC_FINALIZED(op) && Py_TYPE(op)->tp_finalize != NULL) {
|
||||
has_finalizers = true;
|
||||
if (!_PyGen_FinalizerIsNoop(op)) {
|
||||
*needs_resurrection_check = true;
|
||||
}
|
||||
}
|
||||
if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) {
|
||||
continue;
|
||||
}
|
||||
@@ -1590,12 +1598,14 @@ find_weakref_callbacks(struct collection_state *state)
|
||||
|
||||
// Enqueue weakref to be called later.
|
||||
worklist_push(&state->wrcb_to_call, (PyObject *)wr);
|
||||
*needs_resurrection_check = true;
|
||||
}
|
||||
}
|
||||
return has_finalizers;
|
||||
}
|
||||
|
||||
// Clear weakrefs to objects in the unreachable set. See comments
|
||||
// above find_weakref_callbacks() for why this clearing is required.
|
||||
// above prepare_finalization() for why this clearing is required.
|
||||
static void
|
||||
clear_weakrefs(struct collection_state *state)
|
||||
{
|
||||
@@ -2132,29 +2142,40 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
|
||||
gc_visit_heaps(interp, &validate_alive_bits, &state->base);
|
||||
#endif
|
||||
|
||||
// Deallocations after the refcount merge may run Python code.
|
||||
bool needs_resurrection_check = state->objs_to_decref.head != 0;
|
||||
|
||||
// Print debugging information.
|
||||
if (interp->gc.debug & _PyGC_DEBUG_COLLECTABLE) {
|
||||
needs_resurrection_check = true;
|
||||
PyObject *op;
|
||||
WORKSTACK_FOR_EACH(&state->unreachable, op) {
|
||||
debug_cycle("collectable", op);
|
||||
}
|
||||
}
|
||||
|
||||
// Find weakref callbacks we will honor (but do not call them).
|
||||
find_weakref_callbacks(state);
|
||||
_PyEval_StartTheWorld(interp);
|
||||
// Find weakref callbacks and pending finalizers (but do not call them).
|
||||
bool has_finalizers = prepare_finalization(state, &needs_resurrection_check);
|
||||
if (needs_resurrection_check) {
|
||||
_PyEval_StartTheWorld(interp);
|
||||
|
||||
// Deallocate any object from the refcount merge step
|
||||
cleanup_worklist(&state->objs_to_decref);
|
||||
// Deallocate any object from the refcount merge step
|
||||
cleanup_worklist(&state->objs_to_decref);
|
||||
|
||||
// Call weakref callbacks and finalizers after unpausing other threads to
|
||||
// avoid potential deadlocks.
|
||||
call_weakref_callbacks(state);
|
||||
finalize_garbage(state);
|
||||
// Call weakref callbacks and finalizers after unpausing other threads to
|
||||
// avoid potential deadlocks.
|
||||
call_weakref_callbacks(state);
|
||||
finalize_garbage(state);
|
||||
|
||||
_PyEval_StopTheWorld(interp);
|
||||
// Handle any objects that may have resurrected after the finalization.
|
||||
err = handle_resurrected_objects(state);
|
||||
_PyEval_StopTheWorld(interp);
|
||||
// Handle any objects that may have resurrected after the finalization.
|
||||
err = handle_resurrected_objects(state);
|
||||
}
|
||||
else if (has_finalizers) {
|
||||
// Only no-op generator finalizers remain; keep other threads paused.
|
||||
finalize_garbage(state);
|
||||
}
|
||||
// Otherwise, keep other threads paused so the unreachable set stays valid.
|
||||
// Clear free lists in all threads
|
||||
_PyGC_ClearAllFreeLists(interp);
|
||||
if (err == 0) {
|
||||
|
||||
Reference in new issue
Block a user