Skip to content

Commit

Permalink
gc: implement stop-the-world GC
Browse files Browse the repository at this point in the history
  • Loading branch information
colesbury committed Apr 23, 2023
1 parent cfecf6f commit 2864b6b
Show file tree
Hide file tree
Showing 16 changed files with 573 additions and 377 deletions.
7 changes: 3 additions & 4 deletions Include/internal/pycore_gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@ static inline int
_PyGC_ShouldCollect(struct _gc_runtime_state *gcstate)
{
Py_ssize_t live = _Py_atomic_load_ssize_relaxed(&gcstate->gc_live);
return (live >= gcstate->gc_threshold &&
Py_ssize_t threshold = _Py_atomic_load_ssize_relaxed(&gcstate->gc_threshold);
return (live >= threshold &&
gcstate->enabled &&
gcstate->gc_threshold &&
!gcstate->collecting);
threshold);
}

// Functions to clear types free lists
Expand All @@ -228,7 +228,6 @@ extern void _PyList_ClearFreeList(PyInterpreterState *interp);
extern void _PyDict_ClearFreeList(PyInterpreterState *interp);
extern void _PyAsyncGen_ClearFreeLists(PyInterpreterState *interp);
extern void _PyContext_ClearFreeList(PyInterpreterState *interp);
extern void _Py_ScheduleGC(PyInterpreterState *interp);
extern void _Py_RunGC(PyThreadState *tstate);

#ifdef __cplusplus
Expand Down
6 changes: 5 additions & 1 deletion Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,11 @@ _PyType_PreHeaderSize(PyTypeObject *tp)
return 0;
}

void _PyObject_GC_Link(PyObject *op);
static inline uint32_t
_Py_REF_PACK_SHARED(Py_ssize_t refcount, int flags)
{
return _Py_STATIC_CAST(uint32_t, (refcount << _Py_REF_SHARED_SHIFT) + flags);
}

// Usage: assert(_Py_CheckSlotResult(obj, "__getitem__", result != NULL));
extern int _Py_CheckSlotResult(
Expand Down
14 changes: 14 additions & 0 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ enum {
EVAL_GC = 1U << 6
};

#define for_each_thread(t) \
for (PyInterpreterState *i = _PyRuntime.interpreters.head; i; i = i->next) \
for (t = i->threads.head; t; t = t->next)

/* Check if the current thread is the main thread.
Use _Py_IsMainInterpreter() to check if it's the main interpreter. */
static inline int
Expand Down Expand Up @@ -184,6 +188,14 @@ _PyThreadState_IsSignalled(PyThreadState *tstate, uintptr_t bit)
return (b & bit) != 0;
}

static inline void
_Py_ScheduleGC(PyThreadState *tstate)
{
if (!_PyThreadState_IsSignalled(tstate, EVAL_GC)) {
_PyThreadState_Signal(tstate, EVAL_GC);
}
}


static inline void
_PyThreadState_UpdateTracingState(PyThreadState *tstate)
Expand All @@ -196,6 +208,8 @@ _PyThreadState_UpdateTracingState(PyThreadState *tstate)


/* Other */
PyAPI_FUNC(void) _PyThreadState_GC_Park(PyThreadState *tstate);
PyAPI_FUNC(void) _PyThreadState_GC_Stop(PyThreadState *tstate);

PyAPI_FUNC(PyThreadState *) _PyThreadState_Swap(
struct _gilstate_runtime_state *gilstate,
Expand Down
20 changes: 20 additions & 0 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ typedef struct pyruntimestate {
/* Is Python fully initialized? Set to 1 by Py_Initialize() */
int initialized;

/* Has Python started the process of stopping all threads? Protected by HEAD_LOCK() */
int stop_the_world_requested;

/* Have all Python threads stopped? */
int stop_the_world;

/* Number of threads that must park themselves to stop-the-world.
Protected by HEAD_LOCK(runtime). */
Py_ssize_t stw_thread_countdown;

/* Signalled when all threads have stopped themselves */
_PyRawEvent stw_stop_event;

int gc_collecting;

/* Set by Py_FinalizeEx(). Only reset to NULL if Py_Initialize()
is called again.
Expand Down Expand Up @@ -194,6 +209,8 @@ typedef struct pyruntimestate {
/* PyInterpreterState.interpreters.main */
PyInterpreterState _main_interpreter;

_PyMutex stoptheworld_mutex;

Py_ssize_t ref_total;
} _PyRuntimeState;

Expand All @@ -213,6 +230,9 @@ PyAPI_FUNC(void) _PyRuntimeState_Fini(_PyRuntimeState *runtime);
extern PyStatus _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime);
#endif

PyAPI_FUNC(void) _PyRuntimeState_StopTheWorld(_PyRuntimeState *runtime);
PyAPI_FUNC(void) _PyRuntimeState_StartTheWorld(_PyRuntimeState *runtime);

PyAPI_FUNC(Py_ssize_t) _PyRuntimeState_GetRefTotal(_PyRuntimeState *runtime);

/* Initialize _PyRuntimeState.
Expand Down
1 change: 1 addition & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ PyAPI_FUNC(int) PyCallable_Check(PyObject *);
PyAPI_FUNC(void) PyObject_ClearWeakRefs(PyObject *);
#ifndef Py_LIMITED_API
PyAPI_FUNC(void) _PyObject_ClearWeakRefsFromDealloc(PyObject *);
PyAPI_FUNC(void) _PyObject_ClearWeakRefsFromGC(PyObject *);
#endif

/* PyObject_Dir(obj) acts like Python builtins.dir(obj), returning a
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,6 @@ def __init__(self, f, test):
def run(self):
del self.f
gc_collect()
self.test.assertEqual(LAST_FREED, 500)

SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500))
tt = ThreadTest(f, self)
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_concurrent_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1036,7 +1036,10 @@ def test_ressources_gced_in_workers(self):
self.assertTrue(obj.event.wait(timeout=1))

# explicitly destroy the object to ensure that EventfulGCObj.__del__()
# is called while manager is still running.
# is called while manager is still running. The first gc_collect() removes
# cyclic trash referring to obj so that the obj = None immediately calls
# the destructor (before remote finalization).
support.gc_collect()
obj = None
support.gc_collect()

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,7 +1162,7 @@ def test_refcount_errors(self):
p.stderr.close()
# Verify that stderr has a useful error message:
self.assertRegex(stderr,
br'gcmodule\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.')
br'gcmodule\.c:[0-9]+: .*Assertion "gc_get_refs\(gc\) > 0" failed.')
self.assertRegex(stderr,
br'refcount is too small')
# "address : 0x7fb5062efc18"
Expand Down
Loading

0 comments on commit 2864b6b

Please sign in to comment.