Skip to content

Commit f972637

Browse files
GC executors
1 parent cb87676 commit f972637

File tree

4 files changed

+64
-7
lines changed

4 files changed

+64
-7
lines changed

Include/internal/pycore_optimizer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,12 @@ PyAPI_FUNC(void) _Py_Executors_InvalidateDependencyLockHeld(PyInterpreterState *
8282
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);
8383
PyAPI_FUNC(void) _Py_Executors_InvalidateAllLockHeld(PyInterpreterState *interp, int is_invalidation);
8484
PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyThreadState *tstate);
85+
PyAPI_FUNC(void) _Py_Executors_InvalidateColdGC(PyInterpreterState *interp);
8586
#else
8687
# define _Py_Executors_InvalidateDependency(A, B, C) ((void)0)
8788
# define _Py_Executors_InvalidateAll(A, B) ((void)0)
8889
# define _Py_Executors_InvalidateCold(A) ((void)0)
89-
90+
# define _Py_Executors_InvalidateColdGC(A) ((void)0)
9091
#endif
9192

9293
// Used as the threshold to trigger executor invalidation when

Python/gc.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "pycore_pystate.h" // _PyThreadState_GET()
1313
#include "pycore_tuple.h" // _PyTuple_MaybeUntrack()
1414
#include "pycore_weakref.h" // _PyWeakref_ClearRef()
15+
#include "pycore_optimizer.h" // _Py_Executors_InvalidateColdGC
1516

1617
#include "pydtrace.h"
1718

@@ -1735,6 +1736,7 @@ gc_collect_full(PyThreadState *tstate,
17351736
gcstate->old[1].count = 0;
17361737
completed_scavenge(gcstate);
17371738
_PyGC_ClearAllFreeLists(tstate->interp);
1739+
_Py_Executors_InvalidateColdGC(tstate->interp);
17381740
validate_spaces(gcstate);
17391741
add_stats(gcstate, 2, stats);
17401742
}

Python/gc_free_threading.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "pycore_interp.h" // PyInterpreterState.gc
1111
#include "pycore_interpframe.h" // _PyFrame_GetLocalsArray()
1212
#include "pycore_object_alloc.h" // _PyObject_MallocWithType()
13+
#include "pycore_optimizer.h"
1314
#include "pycore_pystate.h" // _PyThreadState_GET()
1415
#include "pycore_tstate.h" // _PyThreadStateImpl
1516
#include "pycore_tuple.h" // _PyTuple_MaybeUntrack()
@@ -2310,6 +2311,8 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
23102311
}
23112312
_PyEval_StartTheWorld(interp);
23122313

2314+
_Py_Executors_InvalidateColdGC(interp);
2315+
23132316
if (err < 0) {
23142317
cleanup_worklist(&state->unreachable);
23152318
cleanup_worklist(&state->legacy_finalizers);

Python/optimizer.c

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,8 +1797,8 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
17971797
// Unlike _PyExecutor_InvalidateDependency, this is not for correctness but memory savings.
17981798
// Thus there is no need to lock the runtime or traverse everything. We simply make a
17991799
// best-effort attempt to clean things up.
1800-
void
1801-
_Py_Executors_InvalidateCold(PyThreadState *tstate)
1800+
PyObject *
1801+
_Py_Executors_CollectCold(PyThreadState *tstate)
18021802
{
18031803
/* Walk the list of executors */
18041804
/* TO DO -- Use a tree to avoid traversing as many objects */
@@ -1813,8 +1813,8 @@ _Py_Executors_InvalidateCold(PyThreadState *tstate)
18131813
_PyExecutorObject *next = exec->vm_data.links.next;
18141814

18151815
if (!exec->vm_data.warm || !exec->vm_data.valid) {
1816+
FT_ATOMIC_STORE_UINT8(exec->vm_data.valid, 0);
18161817
if (PyList_Append(invalidate, (PyObject *)exec) < 0) {
1817-
FT_ATOMIC_STORE_UINT8(exec->vm_data.valid, 0);
18181818
goto error;
18191819
}
18201820
}
@@ -1824,19 +1824,70 @@ _Py_Executors_InvalidateCold(PyThreadState *tstate)
18241824

18251825
exec = next;
18261826
}
1827+
return invalidate;
1828+
error:
1829+
PyErr_Clear();
1830+
Py_XDECREF(invalidate);
1831+
return NULL;
1832+
}
1833+
1834+
void
1835+
_Py_Executors_ClearColdList(PyObject *invalidate)
1836+
{
1837+
if (invalidate == NULL) {
1838+
return;
1839+
}
18271840
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) {
18281841
PyObject *exec = PyList_GET_ITEM(invalidate, i);
18291842
executor_clear(exec);
18301843
}
18311844
Py_DECREF(invalidate);
1845+
}
1846+
1847+
void
1848+
_Py_Executors_InvalidateCold(PyThreadState *tstate)
1849+
{
1850+
_Py_Executors_ClearColdList(_Py_Executors_CollectCold(tstate));
1851+
}
1852+
1853+
void
1854+
_Py_Executors_InvalidateColdGC(PyInterpreterState *interp)
1855+
{
1856+
PyObject *invalidate = PyList_New(0);
1857+
if (invalidate == NULL) {
1858+
PyErr_Clear();
1859+
return;
1860+
}
1861+
_PyEval_StopTheWorld(interp);
1862+
HEAD_LOCK(interp->runtime);
1863+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p)
1864+
{
1865+
PyObject *more_invalidate = _Py_Executors_CollectCold(p);
1866+
if (more_invalidate == NULL) {
1867+
goto error;
1868+
}
1869+
int err = PyList_Extend(invalidate, more_invalidate);
1870+
Py_DECREF(more_invalidate);
1871+
if (err) {
1872+
goto error;
1873+
}
1874+
}
1875+
HEAD_UNLOCK(interp->runtime);
1876+
_PyEval_StartTheWorld(interp);
1877+
// We can only clear the list after unlocking the runtime.
1878+
// Otherwise, it may deadlock.
1879+
_Py_Executors_ClearColdList(invalidate);
18321880
return;
18331881
error:
18341882
PyErr_Clear();
1835-
Py_XDECREF(invalidate);
1836-
// If we're truly out of memory, wiping out everything is a fine fallback
1837-
_Py_Executors_InvalidateAll(_PyInterpreterState_GET(), 0);
1883+
Py_DECREF(invalidate);
1884+
// Invalidate all the executors if we run out of memory.
1885+
// This means the next run will remove all executors.
1886+
_Py_Executors_InvalidateAllLockHeld(interp, 0);
1887+
HEAD_UNLOCK(interp->runtime);
18381888
}
18391889

1890+
18401891
static void
18411892
write_str(PyObject *str, FILE *out)
18421893
{

0 commit comments

Comments
 (0)