Skip to content

gh-145921: Add "_DuringGC" functions for tp_traverse#145925

Open
encukou wants to merge 5 commits intopython:mainfrom
encukou:during-gc
Open

gh-145921: Add "_DuringGC" functions for tp_traverse#145925
encukou wants to merge 5 commits intopython:mainfrom
encukou:during-gc

Conversation

@encukou
Copy link
Member

@encukou encukou commented Mar 13, 2026

There are newly documented restrictions on tp_traverse:

The traversal function must not have any side effects.
It must not modify the reference counts of any Python
objects nor create or destroy any Python objects.

  • Add several functions that are guaranteed side-effect-free, with a _DuringGC suffix.
  • Use these in ctypes
  • Consolidate tp_traverse docs in gcsupport.rst, moving unique content from typeobj.rst there

📚 Documentation preview 📚: https://cpython-previews--145925.org.readthedocs.build/

There are newly documented restrictions on tp_traverse:

    The traversal function must not have any side effects.
    It must not modify the reference counts of any Python
    objects nor create or destroy any Python objects.

* Add several functions that are guaranteed side-effect-free,
  with a _DuringGC suffix.
* Use these in ctypes
* Consolidate tp_traverse docs in gcsupport.rst, moving unique
  content from typeobj.rst there
Copy link
Member

@zooba zooba left a comment

Choose a reason for hiding this comment

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

Mostly doc suggestions, the actual change LGTM!

Comment on lines +300 to +302
The traversal function has a limitation:

.. warning::
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this kind of structure flows well in the docs? It probably works better to go straight to the warning, then in the paragraph after it replace "This means" with "The limitation on side effects means ..."

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, I'll push back here.
The red box looks like it stands on its own. Maybe it's my banner blindness that kicks in, but my brain doesn't immediately connect "The limitation on side effects" to the box above it, so I added the line to make it a part of the running text more obviously.

effects may start having them in future versions, without warning.

For a list of safe functions, see a
:ref:`separate section <durniggc-functions>` below.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
:ref:`separate section <durniggc-functions>` below.
:ref:`separate section <duringgc-functions>` below.

If this didn't trigger a build warning, maybe you've got other typos?

Copy link
Member

Choose a reason for hiding this comment

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

Also, I prefer to name the section even when it's linked, so that someone can find it or recognise it later without actually having to click the link.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the catch!
The other typo is in the link target itself ;)

.. note::

The :c:member:`~PyTypeObject.tp_traverse` function can be called from any
thread.
Copy link
Member

Choose a reason for hiding this comment

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

Should we mention something about "though all other Python threads are guaranteed to be blocked at the time" here?

Copy link
Member Author

Choose a reason for hiding this comment

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

AFAIK that's an implementation detail some people want to get rid of.

Comment on lines +394 to +395
Note that these functions may fail (return ``NULL`` or -1).
Only creating and setting the exception is suppressed.
Copy link
Member

Choose a reason for hiding this comment

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

If they fail during tp_traverse, should we be bailing out as quickly as possible? What value should we return?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good question; honestly I'm not sure about the intentions behind tp_traverse's result.

@pablogsal: would it be correct to say tp_traverse should return zero on recoverable errors, including being unable to visit some objects?
Currently gc.c ignores errors*, but non-zero a result makes VISIT return early; gc_free_threading.c calls PyErr_NoMemory if traversal fails.


* except _PyGC_GetReferrers (gc.get_referrers) which instead assumes that tp_traverse sets an exception on failure -- that's wrong, right?

Comment on lines +394 to +395
Note that these functions may fail (return ``NULL`` or -1).
Only creating and setting the exception is suppressed.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Note that these functions may fail (return ``NULL`` or -1).
Only creating and setting the exception is suppressed.
Note that these functions may fail (return ``NULL`` or -1),
but no exception is set and no error information is available.

Slight rephrasing to be a little less ambiguous, though I'm not totally committed to that wording - you might have something better. There are a few other places this phrase appears below

Copy link
Member Author

Choose a reason for hiding this comment

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

With additional info on error handling, it makes sense to only have this in one place.

PyObject *m = PyType_GetModuleByDef(Py_TYPE(self), &def_nonmodule);
assert(PyErr_Occurred());
assert(m == NULL);
return NULL;
Copy link
Member

Choose a reason for hiding this comment

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

Returning m is probably the easiest way to avoid those warnings, and to avoid an actual refleak if the function passes but assertions are disabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants