Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-102013: Add PyUnstable_GC_VisitObjects #102014

Merged
merged 22 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c4a91e0
Add PyGC_VisitObjects
jbower-fb Feb 8, 2023
57edf6d
📜🤖 Added by blurb_it.
blurb-it[bot] Feb 18, 2023
447bb53
Remove from Limited API
jbower-fb Feb 18, 2023
d4f9505
Merge branch 'main' into gcvisitobjs
hauntsaninja Feb 20, 2023
d34a900
Updated to "unstable" API, protect against GC and current object dele…
jbower-fb Feb 23, 2023
7c1fbbb
Merge branch 'gcvisitobjs' of https://github.com/jbower-fb/cpython-jb…
jbower-fb Feb 23, 2023
9b0c461
Fix formatting in Modules/_testcapimodule.c
jbower-fb Feb 23, 2023
29f7f88
Fix formatting in Modules/_testcapimodule.c
jbower-fb Feb 23, 2023
e16f1e3
Fix formatting in Modules/_testcapimodule.c
jbower-fb Feb 23, 2023
024ee30
Fix formatting in Modules/_testcapimodule.c
jbower-fb Feb 23, 2023
bd65abb
Fix formatting in Modules/gcmodule.c
jbower-fb Feb 23, 2023
f03e581
Fix formatting in Modules/gcmodule.c
jbower-fb Feb 23, 2023
16ea0d6
Fix formatting in Modules/gcmodule.c
jbower-fb Feb 23, 2023
369e199
Simplify test in Modules/_testcapimodule.c
jbower-fb Feb 23, 2023
219154a
Address minor review comments and add documentation.
jbower-fb Feb 23, 2023
be5b7f0
Merge branch 'main' into gcvisitobjs
jbower-fb Feb 24, 2023
5752f1c
Update Doc/c-api/gcsupport.rst
jbower-fb Mar 11, 2023
1512004
Update Modules/_testcapimodule.c
jbower-fb Mar 11, 2023
87a5b56
Update Modules/_testcapimodule.c
jbower-fb Mar 11, 2023
295f7dd
Minor doc tweaks from review
jbower-fb Mar 11, 2023
ed06dff
Merge branch 'python:main' into gcvisitobjs
jbower-fb Mar 11, 2023
620043b
Merge branch 'main' into gcvisitobjs
jbower-fb Mar 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Doc/c-api/gcsupport.rst
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,36 @@ garbage collection runs.
Returns the current state, 0 for disabled and 1 for enabled.

.. versionadded:: 3.10


Querying Garbage Collector State
--------------------------------

The C-API provides the following interface for querying information about
the garbage collector.

.. c:function:: void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)

Run supplied *callback* on all live GC-capable objects. *arg* is passed through to
all invocations of *callback*.

.. warning::
If new objects are (de)allocated by the callback it is undefined if they
will be visited.

Garbage collection is disabled during operation. Explicitly running a collection
in the callback may lead to undefined behaviour e.g. visiting the same objects
multiple times or not at all.

.. versionadded:: 3.12

.. c:type:: int (*gcvisitobjects_t)(PyObject *object, void *arg)

Type of the visitor function to be passed to :c:func:`PyUnstable_GC_VisitObjects`.
*arg* is the same as the *arg* passed to ``PyUnstable_GC_VisitObjects``.
Return ``0`` to continue iteration, return ``1`` to stop iteration. Other return
values are reserved for now so behavior on returning anything else is undefined.

.. versionadded:: 3.12


19 changes: 19 additions & 0 deletions Include/objimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@ PyAPI_FUNC(int) PyGC_Enable(void);
PyAPI_FUNC(int) PyGC_Disable(void);
PyAPI_FUNC(int) PyGC_IsEnabled(void);


#if !defined(Py_LIMITED_API)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can put this in Include/cpython/objimpl.h, which is only included for non-limited API. But that's not a blocker.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very familiar with the conventions here, is that what we should do with public API (even if it's unstable)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/* Visit all live GC-capable objects, similar to gc.get_objects(None). The
* supplied callback is called on every such object with the void* arg set
* to the supplied arg. Returning 0 from the callback ends iteration, returning
* 1 allows iteration to continue. Returning any other value may result in
* undefined behaviour.
*
* If new objects are (de)allocated by the callback it is undefined if they
* will be visited.

* Garbage collection is disabled during operation. Explicitly running a
* collection in the callback may lead to undefined behaviour e.g. visiting the
* same objects multiple times or not at all.
jbower-fb marked this conversation as resolved.
Show resolved Hide resolved
*/
typedef int (*gcvisitobjects_t)(PyObject*, void*);
PyAPI_FUNC(void) PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void* arg);
#endif

/* Test if a type has a GC head */
#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new (unstable) C-API function for iterating over GC'able objects using a callback: ``PyUnstable_VisitObjects``.
69 changes: 69 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3310,6 +3310,73 @@ function_set_kw_defaults(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}

struct gc_visit_state_basic {
PyObject *target;
int found;
};

static int
gc_visit_callback_basic(PyObject *obj, void *arg)
{
struct gc_visit_state_basic *state = (struct gc_visit_state_basic *)arg;
if (obj == state->target) {
state->found = 1;
return 0;
}
return 1;
}

static PyObject *
test_gc_visit_objects_basic(PyObject *Py_UNUSED(self),
PyObject *Py_UNUSED(ignored))
{
PyObject *obj;
struct gc_visit_state_basic state;

obj = PyList_New(0);
if (obj == NULL) {
return NULL;
}
state.target = obj;
state.found = 0;

PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state);
Py_DECREF(obj);
if (!state.found) {
PyErr_SetString(
PyExc_AssertionError,
"test_gc_visit_objects_basic: Didn't find live list");
return NULL;
}
Py_RETURN_NONE;
}

static int
gc_visit_callback_exit_early(PyObject *obj, void *arg)
{
int *visited_i = (int *)arg;
(*visited_i)++;
if (*visited_i == 2) {
return 0;
}
return 1;
}

static PyObject *
test_gc_visit_objects_exit_early(PyObject *Py_UNUSED(self),
PyObject *Py_UNUSED(ignored))
{
int visited_i = 0;
PyUnstable_GC_VisitObjects(gc_visit_callback_exit_early, &visited_i);
if (visited_i != 2) {
PyErr_SetString(
PyExc_AssertionError,
"test_gc_visit_objects_exit_early: did not exit when expected");
}
Py_RETURN_NONE;
}


static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *);

static PyMethodDef TestMethods[] = {
Expand Down Expand Up @@ -3452,6 +3519,8 @@ static PyMethodDef TestMethods[] = {
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"test_gc_visit_objects_basic", test_gc_visit_objects_basic, METH_NOARGS, NULL},
{"test_gc_visit_objects_exit_early", test_gc_visit_objects_exit_early, METH_NOARGS, NULL},
{NULL, NULL} /* sentinel */
};

Expand Down
24 changes: 24 additions & 0 deletions Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2401,3 +2401,27 @@ PyObject_GC_IsFinalized(PyObject *obj)
}
return 0;
}

void
PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
{
size_t i;
GCState *gcstate = get_gc_state();
int origenstate = gcstate->enabled;
gcstate->enabled = 0;
for (i = 0; i < NUM_GENERATIONS; i++) {
PyGC_Head *gc_list, *gc;
gc_list = GEN_HEAD(gcstate, i);
for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) {
PyObject *op = FROM_GC(gc);
Py_INCREF(op);
int res = callback(op, arg);
Py_DECREF(op);
if (!res) {
goto done;
}
}
}
done:
gcstate->enabled = origenstate;
}