Skip to content

Commit 9319acd

Browse files
committed
gh-153946: Refine automatic GC deferral
1 parent c9173ae commit 9319acd

7 files changed

Lines changed: 30 additions & 36 deletions

File tree

Include/internal/pycore_gc.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,9 @@ extern PyObject *_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs);
331331
// Functions to clear types free lists
332332
extern void _PyGC_ClearAllFreeLists(PyInterpreterState *interp);
333333

334-
// These calls nest across all threads in an interpreter. Allocation counters
335-
// continue advancing, explicit collections remain enabled, and a collection
336-
// that becomes due remains eligible at the next normal scheduling opportunity.
337-
// An automatic collection already starting concurrently may emit callbacks,
338-
// but the free-threaded collector will not traverse the protected object graph.
334+
// Nesting is interpreter-wide. Explicit collections and allocation counting
335+
// continue, and resuming does not schedule a collection immediately.
336+
// An in-flight free-threaded collection may still emit start and stop callbacks.
339337
PyAPI_FUNC(void) _PyGC_DeferAutomaticCollection(PyThreadState *tstate);
340338
PyAPI_FUNC(void) _PyGC_ResumeAutomaticCollection(PyThreadState *tstate);
341339
extern void _Py_RunGC(PyThreadState *tstate);

Lib/test/test_gc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def test_defer_automatic_collection_nested(self):
144144
gc.disable()
145145

146146
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
147+
@threading_helper.requires_working_threading()
147148
def test_defer_automatic_collection_across_threads(self):
148149
was_enabled = gc.isenabled()
149150
gc.enable()
@@ -158,6 +159,7 @@ def test_defer_automatic_collection_across_threads(self):
158159
[] for _ in range(10_000)))
159160
thread.start()
160161
thread.join()
162+
self.assertEqual(len(objects), 10_000)
161163
self.assertEqual(self.total_collections(), before)
162164
finally:
163165
_testinternalcapi.resume_automatic_gc()

Parser/asdl_c.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2118,7 +2118,6 @@ class PartingShots(StaticVisitor):
21182118
return NULL;
21192119
}
21202120
PyThreadState *tstate = _PyThreadState_GET();
2121-
// The new objects cannot be cyclic until the completed tree is returned.
21222121
_PyGC_DeferAutomaticCollection(tstate);
21232122
PyObject *result = ast2obj_mod(state, t);
21242123
_PyGC_ResumeAutomaticCollection(tstate);

Python/Python-ast.c

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/gc.c

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,20 +1787,16 @@ void
17871787
_PyGC_DeferAutomaticCollection(PyThreadState *tstate)
17881788
{
17891789
GCState *gcstate = &tstate->interp->gc;
1790-
int previous = _Py_atomic_add_int(
1791-
&gcstate->automatic_collection_pause_count, 1);
1792-
(void)previous;
1793-
assert(previous >= 0);
1790+
assert(gcstate->automatic_collection_pause_count >= 0);
1791+
gcstate->automatic_collection_pause_count++;
17941792
}
17951793

17961794
void
17971795
_PyGC_ResumeAutomaticCollection(PyThreadState *tstate)
17981796
{
17991797
GCState *gcstate = &tstate->interp->gc;
1800-
int previous = _Py_atomic_add_int(
1801-
&gcstate->automatic_collection_pause_count, -1);
1802-
(void)previous;
1803-
assert(previous > 0);
1798+
assert(gcstate->automatic_collection_pause_count > 0);
1799+
gcstate->automatic_collection_pause_count--;
18041800
}
18051801

18061802
/* Public API to invoke gc.collect() from C */
@@ -2003,10 +1999,9 @@ _PyObject_GC_Link(PyObject *op)
20031999
gc->_gc_prev = 0;
20042000
gcstate->generations[0].count++; /* number of allocated GC objects */
20052001
if (gcstate->generations[0].count > gcstate->generations[0].threshold &&
2006-
gcstate->enabled &&
2007-
!_Py_atomic_load_int_relaxed(
2008-
&gcstate->automatic_collection_pause_count) &&
20092002
gcstate->generations[0].threshold &&
2003+
gcstate->enabled &&
2004+
!gcstate->automatic_collection_pause_count &&
20102005
!_Py_atomic_load_int_relaxed(&gcstate->collecting) &&
20112006
!_PyErr_Occurred(tstate))
20122007
{
@@ -2019,8 +2014,7 @@ _Py_RunGC(PyThreadState *tstate)
20192014
{
20202015
GCState *gcstate = get_gc_state();
20212016
if (!gcstate->enabled ||
2022-
_Py_atomic_load_int_relaxed(
2023-
&gcstate->automatic_collection_pause_count)) {
2017+
gcstate->automatic_collection_pause_count) {
20242018
return;
20252019
}
20262020
gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP);

Python/gc_free_threading.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,10 +2001,12 @@ gc_should_collect(GCState *gcstate)
20012001
{
20022002
int count = _Py_atomic_load_int_relaxed(&gcstate->young.count);
20032003
int threshold = gcstate->young.threshold;
2004-
int gc_enabled = _Py_atomic_load_int_relaxed(&gcstate->enabled);
2005-
int pause_count = _Py_atomic_load_int_relaxed(
2006-
&gcstate->automatic_collection_pause_count);
2007-
if (count <= threshold || threshold == 0 || !gc_enabled || pause_count) {
2004+
if (count <= threshold || threshold == 0) {
2005+
return false;
2006+
}
2007+
if (!_Py_atomic_load_int_relaxed(&gcstate->enabled) ||
2008+
_Py_atomic_load_int_relaxed(
2009+
&gcstate->automatic_collection_pause_count)) {
20082010
return false;
20092011
}
20102012
if (gcstate->old[0].threshold == 0) {
@@ -2073,8 +2075,7 @@ gc_collect_internal(PyInterpreterState *interp,
20732075
{
20742076
_PyEval_StopTheWorld(interp);
20752077

2076-
// A concurrent deferral may begin after this collection has emitted its
2077-
// start notification, but it must take effect before any heap traversal.
2078+
// Close the race with a deferral that started before the world stopped.
20782079
if (state->reason == _Py_GC_REASON_HEAP &&
20792080
_Py_atomic_load_int(
20802081
&state->gcstate->automatic_collection_pause_count)) {
@@ -2246,8 +2247,6 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
22462247
s->object_stats.object_visits = 0;
22472248
}
22482249
#endif
2249-
GC_STAT_ADD(generation, collections, 1);
2250-
22512250
if (reason != _Py_GC_REASON_SHUTDOWN) {
22522251
invoke_gc_callback(tstate, "start", generation, 0, 0, 0, 0.0);
22532252
}
@@ -2282,6 +2281,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
22822281
_Py_atomic_store_int(&gcstate->collecting, 0);
22832282
return 0;
22842283
}
2284+
GC_STAT_ADD(generation, collections, 1);
22852285

22862286
m = state.collected;
22872287
n = state.uncollectable;

Python/marshal.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,26 +1765,28 @@ static PyObject *
17651765
read_object(RFILE *p)
17661766
{
17671767
PyObject *v;
1768+
int from_memory = p->ptr && p->end;
17681769
if (PyErr_Occurred()) {
17691770
fprintf(stderr, "XXX readobject called with exception set\n");
17701771
return NULL;
17711772
}
1772-
if (p->ptr && p->end) {
1773+
if (from_memory) {
17731774
if (PySys_Audit("marshal.loads", "y#", p->ptr, (Py_ssize_t)(p->end - p->ptr)) < 0) {
17741775
return NULL;
17751776
}
1776-
PyThreadState *tstate = _PyThreadState_GET();
1777-
_PyGC_DeferAutomaticCollection(tstate);
1778-
v = r_object(p);
1779-
_PyGC_ResumeAutomaticCollection(tstate);
17801777
} else if (p->fp || p->readable) {
17811778
if (PySys_Audit("marshal.load", NULL) < 0) {
17821779
return NULL;
17831780
}
1784-
v = r_object(p);
17851781
}
1786-
else {
1787-
v = r_object(p);
1782+
PyThreadState *tstate;
1783+
if (from_memory) {
1784+
tstate = _PyThreadState_GET();
1785+
_PyGC_DeferAutomaticCollection(tstate);
1786+
}
1787+
v = r_object(p);
1788+
if (from_memory) {
1789+
_PyGC_ResumeAutomaticCollection(tstate);
17881790
}
17891791
if (v == NULL && !PyErr_Occurred())
17901792
PyErr_SetString(PyExc_TypeError, "NULL object in marshal data for object");

0 commit comments

Comments
 (0)