I would like to propose adding a narrow, interpreter-level hook that allows CPython’s cyclic GC strategy to be replaced or wrapped by a custom implementation, similar in spirit to the PEP 523 frame evaluation hook (_PyInterpreterState_SetEvalFrameFunc).
Proposal:
Today CPython’s cyclic GC implementation is tightly coupled to Python/gc.c, PyGC_Head, generation lists, and the existing tp_traverse / tp_clear protocol. Projects that want to experiment with alternative collection strategies, such as parallel collection, different scheduling policies, more detailed GC instrumentation, or platform-specific tuning, generally need to carry invasive patches against CPython.
The goal is not to change the default GC behavior, object model, or public Python semantics. The proposed direction is to keep CPython’s current GC as the default implementation, but expose an internal/unstable C-level strategy interface that can be installed per interpreter. For example, CPython could provide a default strategy backed by the existing implementation, while embedders or experimental builds could install a custom strategy:
typedef struct _PyGCStrategy {
Py_ssize_t (*collect)(PyThreadState *tstate, int generation,
_PyGC_Reason reason);
/* Additional hooks could be considered only if needed, such as
initialization/finalization, collection scheduling, statistics,
or delegation back to the default implementation. */
} _PyGCStrategy;
int _PyInterpreterState_SetGCStrategy(PyInterpreterState *interp,
const _PyGCStrategy *strategy);
const _PyGCStrategy *_PyInterpreterState_GetGCStrategy(PyInterpreterState *interp);
An initial version could deliberately be limited to replacing or wrapping collection entry points such as _PyGC_Collect(), while keeping object tracking, PyGC_Head, tp_traverse, tp_clear, finalization behavior, weakref handling, and resurrection semantics under CPython’s existing rules. That would make the hook useful for experimentation without immediately requiring CPython to support arbitrary moving collectors or a stable GC plugin ABI.
Potential benefits:
- Makes alternative GC strategies easier to prototype without maintaining large forks of Python/gc.c.
- Helps embedders and experimental runtimes evaluate parallel, incremental, or workload-specific collection policies while preserving CPython object semantics.
- Provides a cleaner boundary for GC instrumentation and performance experiments.
- Mirrors the successful pattern of the frame evaluation hook: the default runtime remains unchanged, while advanced users can opt into an internal extension point.
Important constraints:
- This should not be part of the Stable ABI.
- The default CPython GC behavior should remain unchanged.
- The first version should be explicitly private/unstable unless maintainers think a public API is appropriate later.
- Custom strategies should be required to preserve CPython-visible GC semantics, including gc.collect(), gc.callbacks, finalization, weakrefs, resurrection behavior, and debug/statistics behavior as far as applicable.
- The hook should be per-interpreter rather than global, matching the direction of CPython runtime isolation.
I am opening this issue to ask whether CPython maintainers would be open to such an extension point, and what minimal hook boundary would be acceptable for experimentation.