-
-
Notifications
You must be signed in to change notification settings - Fork 35k
gh-153946: Add API to defer automatic garbage collection #154188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Add a private, nestable API for temporarily deferring automatic garbage | ||
| collection without changing the interpreter-visible garbage collector state. | ||
| Use it to avoid unproductive collections while converting ASTs to Python | ||
| objects and unmarshalling objects from memory. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1997,8 +1997,12 @@ gc_should_collect(GCState *gcstate) | |||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| int count = _Py_atomic_load_int_relaxed(&gcstate->young.count); | ||||||||||||||||||||||||||||||||||||||
| int threshold = gcstate->young.threshold; | ||||||||||||||||||||||||||||||||||||||
| int gc_enabled = _Py_atomic_load_int_relaxed(&gcstate->enabled); | ||||||||||||||||||||||||||||||||||||||
| if (count <= threshold || threshold == 0 || !gc_enabled) { | ||||||||||||||||||||||||||||||||||||||
| if (count <= threshold || threshold == 0) { | ||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if (!_Py_atomic_load_int_relaxed(&gcstate->enabled) || | ||||||||||||||||||||||||||||||||||||||
| _Py_atomic_load_int_relaxed( | ||||||||||||||||||||||||||||||||||||||
| &gcstate->automatic_collection_pause_count)) { | ||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if (gcstate->old[0].threshold == 0) { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2061,11 +2065,20 @@ record_deallocation(PyThreadState *tstate) | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| static void | ||||||||||||||||||||||||||||||||||||||
| gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, int generation) | ||||||||||||||||||||||||||||||||||||||
| static bool | ||||||||||||||||||||||||||||||||||||||
| gc_collect_internal(PyInterpreterState *interp, | ||||||||||||||||||||||||||||||||||||||
| struct collection_state *state, int generation) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| _PyEval_StopTheWorld(interp); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Close the race with a deferral that started before the world stopped. | ||||||||||||||||||||||||||||||||||||||
| if (state->reason == _Py_GC_REASON_HEAP && | ||||||||||||||||||||||||||||||||||||||
| _Py_atomic_load_int( | ||||||||||||||||||||||||||||||||||||||
| &state->gcstate->automatic_collection_pause_count)) { | ||||||||||||||||||||||||||||||||||||||
| _PyEval_StartTheWorld(interp); | ||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2074
to
+2080
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a similar check before this? Lines 1486 to 1497 in fe0cc2b
This is a very shy question, so please treat it with a huge grain of salt. My hunch is that GC callbacks may still cause race, since they're just regular Python code and may release GIL: Lines 1467 to 1472 in fe0cc2b
I interrogated Claude Opus to show me what is the actual place for invoking and returning from the callbacks, and that's how I arrived at 1486. |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // update collection and allocation counters | ||||||||||||||||||||||||||||||||||||||
| if (generation+1 < NUM_GENERATIONS) { | ||||||||||||||||||||||||||||||||||||||
| state->gcstate->old[generation].count += 1; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2110,7 +2123,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, | |||||||||||||||||||||||||||||||||||||
| if (err < 0) { | ||||||||||||||||||||||||||||||||||||||
| _PyEval_StartTheWorld(interp); | ||||||||||||||||||||||||||||||||||||||
| PyErr_NoMemory(); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2120,7 +2133,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, | |||||||||||||||||||||||||||||||||||||
| if (err < 0) { | ||||||||||||||||||||||||||||||||||||||
| _PyEval_StartTheWorld(interp); | ||||||||||||||||||||||||||||||||||||||
| PyErr_NoMemory(); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| #ifdef GC_DEBUG | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2167,7 +2180,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, | |||||||||||||||||||||||||||||||||||||
| cleanup_worklist(&state->wrcb_to_call); | ||||||||||||||||||||||||||||||||||||||
| cleanup_worklist(&state->objs_to_decref); | ||||||||||||||||||||||||||||||||||||||
| PyErr_NoMemory(); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Call tp_clear on objects in the unreachable set. This will cause | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2177,6 +2190,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Append objects with legacy finalizers to the "gc.garbage" list. | ||||||||||||||||||||||||||||||||||||||
| handle_legacy_finalizers(state); | ||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| static struct gc_generation_stats * | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2229,8 +2243,6 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||||||||||||||||||||||||||||||||||||
| s->object_stats.object_visits = 0; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||
| GC_STAT_ADD(generation, collections, 1); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (reason != _Py_GC_REASON_SHUTDOWN) { | ||||||||||||||||||||||||||||||||||||||
| invoke_gc_callback(tstate, "start", generation, 0, 0, 0, 0.0); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2254,7 +2266,18 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) | |||||||||||||||||||||||||||||||||||||
| .reason = reason, | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| gc_collect_internal(interp, &state, generation); | ||||||||||||||||||||||||||||||||||||||
| if (!gc_collect_internal(interp, &state, generation)) { | ||||||||||||||||||||||||||||||||||||||
| if (PyDTrace_GC_DONE_ENABLED()) { | ||||||||||||||||||||||||||||||||||||||
| PyDTrace_GC_DONE(0); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| if (reason != _Py_GC_REASON_SHUTDOWN) { | ||||||||||||||||||||||||||||||||||||||
| invoke_gc_callback(tstate, "stop", generation, 0, 0, 0, 0.0); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| gcstate->frame = NULL; | ||||||||||||||||||||||||||||||||||||||
| _Py_atomic_store_int(&gcstate->collecting, 0); | ||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| GC_STAT_ADD(generation, collections, 1); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| m = state.collected; | ||||||||||||||||||||||||||||||||||||||
| n = state.uncollectable; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2545,6 +2568,26 @@ PyGC_IsEnabled(void) | |||||||||||||||||||||||||||||||||||||
| return _Py_atomic_load_int_relaxed(&gcstate->enabled); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| void | ||||||||||||||||||||||||||||||||||||||
| _PyGC_DeferAutomaticCollection(PyThreadState *tstate) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| GCState *gcstate = &tstate->interp->gc; | ||||||||||||||||||||||||||||||||||||||
| int previous = _Py_atomic_add_int( | ||||||||||||||||||||||||||||||||||||||
| &gcstate->automatic_collection_pause_count, 1); | ||||||||||||||||||||||||||||||||||||||
| (void)previous; | ||||||||||||||||||||||||||||||||||||||
| assert(previous >= 0); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| void | ||||||||||||||||||||||||||||||||||||||
| _PyGC_ResumeAutomaticCollection(PyThreadState *tstate) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| GCState *gcstate = &tstate->interp->gc; | ||||||||||||||||||||||||||||||||||||||
| int previous = _Py_atomic_add_int( | ||||||||||||||||||||||||||||||||||||||
| &gcstate->automatic_collection_pause_count, -1); | ||||||||||||||||||||||||||||||||||||||
| (void)previous; | ||||||||||||||||||||||||||||||||||||||
| assert(previous > 0); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /* Public API to invoke gc.collect() from C */ | ||||||||||||||||||||||||||||||||||||||
| Py_ssize_t | ||||||||||||||||||||||||||||||||||||||
| PyGC_Collect(void) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pablogsal Another odd question: what about a
fork()in a free-threading build?Let's say that there are two threads. There's a
fork(), and the children inheritautomatic_collection_pause_count. Since these are separate processes, there's no way to recover, unless there's a clean up after fork? (I would have to think, but I'm not sold on naive zeroing, similar toreset_asyncio_stateorreset_remotedebug_data)Truth be told, this is likely a problem with
collectingtoo...If that's the case, I can take a stab.
cc @nascheme