Skip to content

Commit 91a5bd4

Browse files
jbower-fbFidget-Spinner
authored andcommitted
pythongh-102013: Add PyUnstable_GC_VisitObjects (python#102014)
1 parent 5b8c3cb commit 91a5bd4

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed

Doc/c-api/gcsupport.rst

+33
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,36 @@ garbage collection runs.
228228
Returns the current state, 0 for disabled and 1 for enabled.
229229
230230
.. versionadded:: 3.10
231+
232+
233+
Querying Garbage Collector State
234+
--------------------------------
235+
236+
The C-API provides the following interface for querying information about
237+
the garbage collector.
238+
239+
.. c:function:: void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
240+
241+
Run supplied *callback* on all live GC-capable objects. *arg* is passed through to
242+
all invocations of *callback*.
243+
244+
.. warning::
245+
If new objects are (de)allocated by the callback it is undefined if they
246+
will be visited.
247+
248+
Garbage collection is disabled during operation. Explicitly running a collection
249+
in the callback may lead to undefined behaviour e.g. visiting the same objects
250+
multiple times or not at all.
251+
252+
.. versionadded:: 3.12
253+
254+
.. c:type:: int (*gcvisitobjects_t)(PyObject *object, void *arg)
255+
256+
Type of the visitor function to be passed to :c:func:`PyUnstable_GC_VisitObjects`.
257+
*arg* is the same as the *arg* passed to ``PyUnstable_GC_VisitObjects``.
258+
Return ``0`` to continue iteration, return ``1`` to stop iteration. Other return
259+
values are reserved for now so behavior on returning anything else is undefined.
260+
261+
.. versionadded:: 3.12
262+
263+

Include/objimpl.h

+19
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,25 @@ PyAPI_FUNC(int) PyGC_Enable(void);
157157
PyAPI_FUNC(int) PyGC_Disable(void);
158158
PyAPI_FUNC(int) PyGC_IsEnabled(void);
159159

160+
161+
#if !defined(Py_LIMITED_API)
162+
/* Visit all live GC-capable objects, similar to gc.get_objects(None). The
163+
* supplied callback is called on every such object with the void* arg set
164+
* to the supplied arg. Returning 0 from the callback ends iteration, returning
165+
* 1 allows iteration to continue. Returning any other value may result in
166+
* undefined behaviour.
167+
*
168+
* If new objects are (de)allocated by the callback it is undefined if they
169+
* will be visited.
170+
171+
* Garbage collection is disabled during operation. Explicitly running a
172+
* collection in the callback may lead to undefined behaviour e.g. visiting the
173+
* same objects multiple times or not at all.
174+
*/
175+
typedef int (*gcvisitobjects_t)(PyObject*, void*);
176+
PyAPI_FUNC(void) PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void* arg);
177+
#endif
178+
160179
/* Test if a type has a GC head */
161180
#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
162181

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a new (unstable) C-API function for iterating over GC'able objects using a callback: ``PyUnstable_VisitObjects``.

Modules/_testcapimodule.c

+69
Original file line numberDiff line numberDiff line change
@@ -3310,6 +3310,73 @@ function_set_kw_defaults(PyObject *self, PyObject *args)
33103310
Py_RETURN_NONE;
33113311
}
33123312

3313+
struct gc_visit_state_basic {
3314+
PyObject *target;
3315+
int found;
3316+
};
3317+
3318+
static int
3319+
gc_visit_callback_basic(PyObject *obj, void *arg)
3320+
{
3321+
struct gc_visit_state_basic *state = (struct gc_visit_state_basic *)arg;
3322+
if (obj == state->target) {
3323+
state->found = 1;
3324+
return 0;
3325+
}
3326+
return 1;
3327+
}
3328+
3329+
static PyObject *
3330+
test_gc_visit_objects_basic(PyObject *Py_UNUSED(self),
3331+
PyObject *Py_UNUSED(ignored))
3332+
{
3333+
PyObject *obj;
3334+
struct gc_visit_state_basic state;
3335+
3336+
obj = PyList_New(0);
3337+
if (obj == NULL) {
3338+
return NULL;
3339+
}
3340+
state.target = obj;
3341+
state.found = 0;
3342+
3343+
PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state);
3344+
Py_DECREF(obj);
3345+
if (!state.found) {
3346+
PyErr_SetString(
3347+
PyExc_AssertionError,
3348+
"test_gc_visit_objects_basic: Didn't find live list");
3349+
return NULL;
3350+
}
3351+
Py_RETURN_NONE;
3352+
}
3353+
3354+
static int
3355+
gc_visit_callback_exit_early(PyObject *obj, void *arg)
3356+
{
3357+
int *visited_i = (int *)arg;
3358+
(*visited_i)++;
3359+
if (*visited_i == 2) {
3360+
return 0;
3361+
}
3362+
return 1;
3363+
}
3364+
3365+
static PyObject *
3366+
test_gc_visit_objects_exit_early(PyObject *Py_UNUSED(self),
3367+
PyObject *Py_UNUSED(ignored))
3368+
{
3369+
int visited_i = 0;
3370+
PyUnstable_GC_VisitObjects(gc_visit_callback_exit_early, &visited_i);
3371+
if (visited_i != 2) {
3372+
PyErr_SetString(
3373+
PyExc_AssertionError,
3374+
"test_gc_visit_objects_exit_early: did not exit when expected");
3375+
}
3376+
Py_RETURN_NONE;
3377+
}
3378+
3379+
33133380
static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);
33143381

33153382
static PyMethodDef TestMethods[] = {
@@ -3452,6 +3519,8 @@ static PyMethodDef TestMethods[] = {
34523519
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
34533520
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
34543521
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
3522+
{"test_gc_visit_objects_basic", test_gc_visit_objects_basic, METH_NOARGS, NULL},
3523+
{"test_gc_visit_objects_exit_early", test_gc_visit_objects_exit_early, METH_NOARGS, NULL},
34553524
{NULL, NULL} /* sentinel */
34563525
};
34573526

Modules/gcmodule.c

+24
Original file line numberDiff line numberDiff line change
@@ -2401,3 +2401,27 @@ PyObject_GC_IsFinalized(PyObject *obj)
24012401
}
24022402
return 0;
24032403
}
2404+
2405+
void
2406+
PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
2407+
{
2408+
size_t i;
2409+
GCState *gcstate = get_gc_state();
2410+
int origenstate = gcstate->enabled;
2411+
gcstate->enabled = 0;
2412+
for (i = 0; i < NUM_GENERATIONS; i++) {
2413+
PyGC_Head *gc_list, *gc;
2414+
gc_list = GEN_HEAD(gcstate, i);
2415+
for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) {
2416+
PyObject *op = FROM_GC(gc);
2417+
Py_INCREF(op);
2418+
int res = callback(op, arg);
2419+
Py_DECREF(op);
2420+
if (!res) {
2421+
goto done;
2422+
}
2423+
}
2424+
}
2425+
done:
2426+
gcstate->enabled = origenstate;
2427+
}

0 commit comments

Comments
 (0)