From bc323989dc16d36319dc602feb2c20a6ef47440a Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 19 Oct 2024 17:38:45 -0400 Subject: [PATCH 1/5] gh-75459: Doc: C API: Improve object life cycle documentation * Add "cyclic isolate" to the glossary. * Add a new "Object Life Cycle" page. * Illustrate the order of life cycle functions. * Document `PyObject_CallFinalizer` and `PyObject_CallFinalizerFromDealloc`. * `PyObject_Init` does not call `tp_init`. * `PyObject_New`: * also initializes the memory * does not call `tp_alloc`, `tp_new`, or `tp_init` * should not be used for GC-enabled objects * memory must be freed by `PyObject_Free` * `PyObject_GC_New` memory must be freed by `PyObject_GC_Del`. * Warn that garbage collector functions can be called from any thread. * `tp_finalize` and `tp_clear`: * Only called when there's a cyclic isolate. * Only one object in the cyclic isolate is finalized/cleared at a time. * Clearly warn that they might not be called. * They can optionally be manually called from `tp_dealloc` (via `PyObject_CallFinalizerFromDealloc` in the case of `tp_finalize`). * `tp_finalize`: * Reference `object.__del__`. * The finalizer can resurrect the object. * Suggest `PyErr_GetRaisedException` and `PyErr_SetRaisedException` instead of the deprecated `PyErr_Fetch` and `PyErr_Restore` functions. * Add links to `PyErr_GetRaisedException` and `PyErr_SetRaisedException`. * Suggest using `PyErr_WriteUnraisable` if an exception is raised during finalization. * Rename the example function from `local_finalize` to `foo_finalize` for consistency with the `tp_dealloc` documentation and as a hint that the name isn't special. * Minor wording and sylistic tweaks. * Warn that `tp_finalize` can be called during shutdown. --- Doc/c-api/allocation.rst | 54 ++++++--- Doc/c-api/gcsupport.rst | 9 ++ Doc/c-api/lifecycle.dot | 96 ++++++++++++++++ Doc/c-api/lifecycle.dot.svg | 220 ++++++++++++++++++++++++++++++++++++ Doc/c-api/lifecycle.rst | 79 +++++++++++++ Doc/c-api/objimpl.rst | 1 + Doc/c-api/typeobj.rst | 153 ++++++++++++++++++++----- Doc/glossary.rst | 6 + 8 files changed, 574 insertions(+), 44 deletions(-) create mode 100644 Doc/c-api/lifecycle.dot create mode 100644 Doc/c-api/lifecycle.dot.svg create mode 100644 Doc/c-api/lifecycle.rst diff --git a/Doc/c-api/allocation.rst b/Doc/c-api/allocation.rst index 0d53b18ea87d5e..4d16b900b6154f 100644 --- a/Doc/c-api/allocation.rst +++ b/Doc/c-api/allocation.rst @@ -18,7 +18,8 @@ Allocating Objects on the Heap reference. Returns the initialized object. If *type* indicates that the object participates in the cyclic garbage detector, it is added to the detector's set of observed objects. Other fields of the object are not - affected. + initialized. Specifically, this function does **not** call the object's + :meth:`~object.__init__` method (:c:member:`~PyTypeObject.tp_init` slot). .. c:function:: PyVarObject* PyObject_InitVar(PyVarObject *op, PyTypeObject *type, Py_ssize_t size) @@ -29,27 +30,44 @@ Allocating Objects on the Heap .. c:macro:: PyObject_New(TYPE, typeobj) - Allocate a new Python object using the C structure type *TYPE* - and the Python type object *typeobj* (``PyTypeObject*``). - Fields not defined by the Python object header are not initialized. - The caller will own the only reference to the object - (i.e. its reference count will be one). - The size of the memory allocation is determined from the - :c:member:`~PyTypeObject.tp_basicsize` field of the type object. + Calls :c:func:`PyObject_Malloc` to allocate memory for a new Python object + using the C structure type *TYPE* and the Python type object *typeobj* + (``PyTypeObject*``), then initializes the memory like + :c:func:`PyObject_Init`. The caller will own the only reference to the + object (i.e. its reference count will be one). The size of the memory + allocation is determined from the :c:member:`~PyTypeObject.tp_basicsize` + field of the type object. + + This does not call :c:member:`~PyTypeObject.tp_alloc`, + :c:member:`~PyTypeObject.tp_new` (:meth:`~object.__new__`), or + :c:member:`~PyTypeObject.tp_init` (:meth:`~object.__init__`). + + This should not be used for objects with :c:macro:`Py_TPFLAGS_HAVE_GC` set + in :c:member:`~PyTypeObject.tp_flags`; use :c:macro:`PyObject_GC_New` + instead. + + Memory allocated by this function must be freed with :c:func:`PyObject_Free`. .. c:macro:: PyObject_NewVar(TYPE, typeobj, size) - Allocate a new Python object using the C structure type *TYPE* and the - Python type object *typeobj* (``PyTypeObject*``). - Fields not defined by the Python object header - are not initialized. The allocated memory allows for the *TYPE* structure - plus *size* (``Py_ssize_t``) fields of the size - given by the :c:member:`~PyTypeObject.tp_itemsize` field of - *typeobj*. This is useful for implementing objects like tuples, which are - able to determine their size at construction time. Embedding the array of - fields into the same allocation decreases the number of allocations, - improving the memory management efficiency. + Like :c:macro:`PyObject_New` except: + + * It allocates enough memory for the *TYPE* structure plus *size* + (``Py_ssize_t``) fields of the size given by the + :c:member:`~PyTypeObject.tp_itemsize` field of *typeobj*. + * The memory is initialized like :c:func:`PyObject_InitVar`. + + This is useful for implementing objects like tuples, which are able to + determine their size at construction time. Embedding the array of fields + into the same allocation decreases the number of allocations, improving the + memory management efficiency. + + This should not be used for objects with :c:macro:`Py_TPFLAGS_HAVE_GC` set + in :c:member:`~PyTypeObject.tp_flags`; use :c:macro:`PyObject_GC_NewVar` + instead. + + Memory allocated by this function must be freed with :c:func:`PyObject_Free`. .. c:function:: void PyObject_Del(void *op) diff --git a/Doc/c-api/gcsupport.rst b/Doc/c-api/gcsupport.rst index 621da3eb069949..93c981407104d4 100644 --- a/Doc/c-api/gcsupport.rst +++ b/Doc/c-api/gcsupport.rst @@ -57,11 +57,17 @@ rules: Analogous to :c:macro:`PyObject_New` but for container objects with the :c:macro:`Py_TPFLAGS_HAVE_GC` flag set. + Memory allocated by this function must be freed with + :c:func:`PyObject_GC_Del`. + .. c:macro:: PyObject_GC_NewVar(TYPE, typeobj, size) Analogous to :c:macro:`PyObject_NewVar` but for container objects with the :c:macro:`Py_TPFLAGS_HAVE_GC` flag set. + Memory allocated by this function must be freed with + :c:func:`PyObject_GC_Del`. + .. c:function:: PyObject* PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *type, size_t extra_size) Analogous to :c:macro:`PyObject_GC_New` but allocates *extra_size* @@ -73,6 +79,9 @@ rules: The extra data will be deallocated with the object, but otherwise it is not managed by Python. + Memory allocated by this function must be freed with + :c:func:`PyObject_GC_Del`. + .. warning:: The function is marked as unstable because the final mechanism for reserving extra data after an instance is not yet decided. diff --git a/Doc/c-api/lifecycle.dot b/Doc/c-api/lifecycle.dot new file mode 100644 index 00000000000000..0c508f8361df58 --- /dev/null +++ b/Doc/c-api/lifecycle.dot @@ -0,0 +1,96 @@ +digraph G { + graph [ + fontname="svg" + fontsize=10.0 + layout="dot" + ranksep=0.25 + ] + node [ + fontname="Courier" + fontsize=10.0 + ] + edge [ + fontname="Times-Italic" + fontsize=10.0 + ] + + "start" [fontname="Times-Italic" shape=plain label=< start > style=invis] + "tp_alloc" [href="typeobj.html#c.PyTypeObject.tp_alloc" target="_top"] + "tp_new" [href="typeobj.html#c.PyTypeObject.tp_new" target="_top"] + "tp_init" [href="typeobj.html#c.PyTypeObject.tp_init" target="_top"] + { + rank="same" + "alive" [ + fontname="Times-Italic" + label= + shape=box + ] + "tp_traverse" [ + href="typeobj.html#c.PyTypeObject.tp_traverse" + shape=octagon + target="_top" + ] + } + "tp_finalize" [ + href="typeobj.html#c.PyTypeObject.tp_finalize" + shape=octagon + target="_top" + ] + "tp_clear" [ + href="typeobj.html#c.PyTypeObject.tp_clear" + shape=octagon + target="_top" + ] + "ref0" [ + fontname="Times-Italic" + label= + ordering="in" + shape=box + ] + "tp_dealloc" [href="typeobj.html#c.PyTypeObject.tp_dealloc" target="_top"] + "tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"] + + "start" -> "tp_alloc" + "tp_alloc" -> "tp_new" + "tp_new" -> "tp_init" + "tp_init" -> "alive" + "tp_traverse" -> "alive" + "alive" -> "tp_traverse" + "alive" -> "tp_clear" [label=< cyclic
isolate >] + "alive" -> "tp_finalize" [ + dir="back" + label=< resurrected > + ] + "alive" -> "tp_finalize" [label=< cyclic
isolate >] + "tp_finalize" -> "tp_clear" + "tp_finalize" -> "ref0" + "tp_clear" -> "ref0" + "tp_clear" -> "tp_dealloc" [ + dir="back" + label=< optional
direct call > + ] + "alive" -> "ref0" + "ref0" -> "tp_dealloc" + "tp_finalize" -> "tp_dealloc" [ + dir="back" + href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" + label=< + + + + + + + + + + + + + +
optional call to
PyObject_Call
FinalizerFrom
Dealloc
+ > + target="_top" + ] + "tp_dealloc" -> "tp_free" [label=< directly calls >] +} diff --git a/Doc/c-api/lifecycle.dot.svg b/Doc/c-api/lifecycle.dot.svg new file mode 100644 index 00000000000000..d97ae0d0e445ce --- /dev/null +++ b/Doc/c-api/lifecycle.dot.svg @@ -0,0 +1,220 @@ + + + + + + +G + + + + +tp_alloc + + +tp_alloc + + + + + +start->tp_alloc + + + + + +tp_new + + +tp_new + + + + + +tp_alloc->tp_new + + + + + +tp_init + + +tp_init + + + + + +tp_new->tp_init + + + + + +alive + +alive, ref count > 0 + + + +tp_init->alive + + + + + +tp_traverse + + +tp_traverse + + + + + +alive->tp_traverse + + + + + +tp_finalize + + +tp_finalize + + + + + +alive->tp_finalize + + +  resurrected   + + + +alive->tp_finalize + + +  cyclic     +isolate     + + + +tp_clear + + +tp_clear + + + + + +alive->tp_clear + + +  cyclic     +isolate     + + + +ref0 + +ref count == 0 + + + +alive->ref0 + + + + + +tp_traverse->alive + + + + + +tp_finalize->tp_clear + + + + + +tp_finalize->ref0 + + + + + +tp_dealloc + + +tp_dealloc + + + + + +tp_finalize->tp_dealloc + + + + + + + +optional call to +       +PyObject_Call +FinalizerFrom +Dealloc + + + + + +tp_clear->ref0 + + + + + +tp_clear->tp_dealloc + + +  optional +direct call   + + + +ref0->tp_dealloc + + + + + +tp_free + + +tp_free + + + + + +tp_dealloc->tp_free + + +  directly calls   + + + diff --git a/Doc/c-api/lifecycle.rst b/Doc/c-api/lifecycle.rst new file mode 100644 index 00000000000000..56f1a386f0760e --- /dev/null +++ b/Doc/c-api/lifecycle.rst @@ -0,0 +1,79 @@ +.. highlight:: c + +.. _life-cycle: + +Object Life Cycle +================= + +Stages +------ + +The following is an illustration of the stages of life of an object. Arrows +indicate a "happens before" relationship. Octagons indicate functions specific +to :ref:`garbage collection support `. + +.. raw:: html + :file: lifecycle.dot.svg + +Explanation: + +* :c:member:`~PyTypeObject.tp_alloc`, :c:member:`~PyTypeObject.tp_new`, and + :c:member:`~PyTypeObject.tp_init` are called to allocate memory for a new + object and initialize the object. +* If the reference count for an object drops to 0, + :c:member:`~PyTypeObject.tp_dealloc` is called to destroy the object. +* :c:member:`~PyTypeObject.tp_dealloc` can optionally call + :c:member:`~PyTypeObject.tp_finalize` (if non-``NULL``) via + :c:func:`PyObject_CallFinalizerFromDealloc` if it wishes to reuse that code + to help with object destruction. +* :c:member:`~PyTypeObject.tp_finalize` may increase the object's reference + count, halting the destruction. The object is said to be resurrected. +* :c:member:`~PyTypeObject.tp_dealloc` can optionally call + :c:member:`~PyTypeObject.tp_clear` (if non-``NULL``) if it wishes to reuse + that code to help with object destruction. +* When :c:member:`~PyTypeObject.tp_dealloc` finishes object destruction, it + directly calls :c:member:`~PyTypeObject.tp_free` to deallocate the memory. + +If the object is marked as supporting garbage collection (the +:c:macro:`Py_TPFLAGS_HAVE_GC` flag is set in +:c:member:`~PyTypeObject.tp_flags`), the following stages are also possible: + +* The garbage collector occasionally calls + :c:member:`~PyTypeObject.tp_traverse` to identify :term:`cyclic isolates + `. +* When the garbage collector discovers a cyclic isolate, it finalizes one of + the objects in the group by calling its :c:member:`~PyTypeObject.tp_finalize` + function. This repeats until the cyclic isolate doesn't exist or all of the + objects have been finalized. +* The :c:member:`~PyTypeObject.tp_finalize` function can optionally increase + the object's reference count, causing it (and other objects it references) to + become resurrected and no longer a member of a cyclic isolate. +* When the garbage collector discovers a cyclic isolate and all of the objects + in the group have already been finalized, the garbage collector clears one of + the objects in the group by calling its :c:member:`~PyTypeObject.tp_clear` + function. This repeats until the cyclic isolate doesn't exist or all of the + objects have been cleared. + + +Functions +--------- + +To allocate and free memory, see :ref:`Allocating Objects on the Heap +`. + + +.. c:function:: void PyObject_CallFinalizer(PyObject *op) + + Calls the object's finalizer (:c:member:`~PyTypeObject.tp_finalize`) if it + has not already been called. + + +.. c:function:: int PyObject_CallFinalizerFromDealloc(PyObject *op) + + Calls the object's finalizer (:c:member:`~PyTypeObject.tp_finalize`) if it + has not already been called. This function is intended to be called at the + beginning of the object's destructor (:c:member:`~PyTypeObject.tp_dealloc`). + The object's reference count must already be 0. If the object's finalizer + increases the object's reference count, the object is resurrected and this + function returns -1; no further destruction should happen. Otherwise, this + function returns 0 and destruction can continue normally. diff --git a/Doc/c-api/objimpl.rst b/Doc/c-api/objimpl.rst index 8bd8c107c98bdf..83de4248039949 100644 --- a/Doc/c-api/objimpl.rst +++ b/Doc/c-api/objimpl.rst @@ -12,6 +12,7 @@ object types. .. toctree:: allocation.rst + lifecycle.rst structures.rst typeobj.rst gcsupport.rst diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 0c59b3da0795cb..e18fea6cb2b9e4 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -1429,6 +1429,11 @@ and :c:data:`PyType_Type` effectively act as defaults.) heap-allocated superclass). If they do not, the type object may not be garbage-collected. + .. warning:: + + The :c:member:`~PyTypeObject.tp_traverse` function can be called from any + thread. + .. versionchanged:: 3.9 Heap-allocated types are expected to visit ``Py_TYPE(self)`` in @@ -1453,15 +1458,29 @@ and :c:data:`PyType_Type` effectively act as defaults.) int tp_clear(PyObject *); - The :c:member:`~PyTypeObject.tp_clear` member function is used to break reference cycles in cyclic - garbage detected by the garbage collector. Taken together, all :c:member:`~PyTypeObject.tp_clear` - functions in the system must combine to break all reference cycles. This is - subtle, and if in any doubt supply a :c:member:`~PyTypeObject.tp_clear` function. For example, - the tuple type does not implement a :c:member:`~PyTypeObject.tp_clear` function, because it's - possible to prove that no reference cycle can be composed entirely of tuples. - Therefore the :c:member:`~PyTypeObject.tp_clear` functions of other types must be sufficient to - break any cycle containing a tuple. This isn't immediately obvious, and there's - rarely a good reason to avoid implementing :c:member:`~PyTypeObject.tp_clear`. + The :c:member:`!tp_clear` function is called by the garbage collector in a + final attempt to break reference cycles in a :term:`cyclic isolate`, after + finalizing all of the objects in the group via + :c:member:`~PyTypeObject.tp_finalize`. The garbage collector calls + :c:member:`!tp_clear` on one of the objects in the cyclic isolate. It is + unspecified which object in the group is cleared, and on which thread + :c:member:`!tp_clear` executes. If a cyclic isolate still exists after the + object is cleared, the garbage collector clears another object from the + group. This repeats until either the cyclic isolate no longer exists or all + of the objects in the group have been cleared. If a cyclic isolate still + remains after clearing all objects in the group, the remaining objects + "leak"—they remain indefinitely uncollectable (see :data:`gc.garbage`). + + Taken together, all :c:member:`~PyTypeObject.tp_clear` functions in the + system must combine to break all reference cycles. This is subtle, and if + in any doubt supply a :c:member:`~PyTypeObject.tp_clear` function. For + example, the tuple type does not implement a + :c:member:`~PyTypeObject.tp_clear` function, because it's possible to prove + that no reference cycle can be composed entirely of tuples. Therefore the + :c:member:`~PyTypeObject.tp_clear` functions of other types must be + sufficient to break any cycle containing a tuple. This isn't immediately + obvious, and there's rarely a good reason to avoid implementing + :c:member:`~PyTypeObject.tp_clear`. Implementations of :c:member:`~PyTypeObject.tp_clear` should drop the instance's references to those of its members that may be Python objects, and set its pointers to those @@ -1510,6 +1529,31 @@ and :c:data:`PyType_Type` effectively act as defaults.) More information about Python's garbage collection scheme can be found in section :ref:`supporting-cycle-detection`. + .. warning:: + + The :c:member:`~PyTypeObject.tp_clear` clear function may or may not be + automatically called depending on: + + * whether the garbage collector is enabled + * whether the object is a member of a :term:`cyclic isolate` + * whether the garbage collector has detected the cyclic isolate (yet) + * whether finalizing or clearing a different object in the same cyclic + isolate dropped the reference count to zero before the garbage + collector would have cleared the object + + To ensure that :c:member:`!tp_clear` is always called before an object is + destroyed, call it directly from :c:member:`~PyTypeObject.tp_dealloc`. + (This does not guarantee that :c:member:`!tp_clear` will be called, only + that it will be called if :c:member:`!tp_dealloc` is called.) Beware + that :c:member:`!tp_clear` might have already been called; it is best to + make :c:member:`!tp_clear` idempotent (:c:func:`Py_CLEAR` is + idempotent). + + .. warning:: + + The :c:member:`~PyTypeObject.tp_clear` function can be called from any + thread. + **Inheritance:** Group: :c:macro:`Py_TPFLAGS_HAVE_GC`, :c:member:`~PyTypeObject.tp_traverse`, :c:member:`~PyTypeObject.tp_clear` @@ -2094,34 +2138,91 @@ and :c:data:`PyType_Type` effectively act as defaults.) .. c:member:: destructor PyTypeObject.tp_finalize - An optional pointer to an instance finalization function. Its signature is:: + An optional pointer to an instance finalization function. This is the C + implementation of the :meth:`~object.__del__` special method. Its signature + is:: void tp_finalize(PyObject *self); - If :c:member:`~PyTypeObject.tp_finalize` is set, the interpreter calls it once when - finalizing an instance. It is called either from the garbage - collector (if the instance is part of an isolated reference cycle) or - just before the object is deallocated. Either way, it is guaranteed - to be called before attempting to break reference cycles, ensuring - that it finds the object in a sane state. - - :c:member:`~PyTypeObject.tp_finalize` should not mutate the current exception status; - therefore, a recommended way to write a non-trivial finalizer is:: + When the garbage collector discovers a :term:`cyclic isolate`, it calls + :c:member:`!tp_finalize` on one of the objects in the group. It is + unspecified which object in the cyclic isolate is finalized, and on which + thread the finalizer executes. If a cyclic isolate still exists after the + finalizer returns, the garbage collector finalizes another object from the + group. This repeats until either the cyclic isolate no longer exists or all + of the objects in the group with a finalizer have been finalized. + + The finalizer can optionally create a reference to the object from outside + the cyclic isolate, which *resurrects* it and the other objects in the + group. It is implementation-defined whether a finalized but resurrected + object can be finalized again; :term:`CPython` currently never calls an + object's finalizer twice. + + All objects in a cyclic isolate are guaranteed to be finalized before + :c:member:`~PyTypeObject.tp_clear` is called on any of the objects (to break + any remaining reference cycles). + + :c:member:`~PyTypeObject.tp_finalize` should leave the current exception + status unchanged. The recommended way to write a non-trivial finalizer is + to back up the exception at the beginning by calling + :c:func:`PyErr_GetRaisedException` and restore the exception at the end by + calling :c:func:`PyErr_SetRaisedException`. If an exception is encountered + in the middle of the finalizer, log and clear it with + :c:func:`PyErr_WriteUnraisable` or :c:func:`PyErr_FormatUnraisable`. For + example:: static void - local_finalize(PyObject *self) + foo_finalize(PyObject *self) { - PyObject *error_type, *error_value, *error_traceback; + // Save the current exception, if any. + PyObject *exc = PyErr_GetRaisedException(); - /* Save the current exception, if any. */ - PyErr_Fetch(&error_type, &error_value, &error_traceback); + // ... - /* ... */ + if (do_something_that_might_raise() != success_indicator) { + PyErr_WriteUnraisable(self); + goto done; + } - /* Restore the saved exception. */ - PyErr_Restore(error_type, error_value, error_traceback); + // ... + + done: + // Restore the saved exception. This silently discards any exception + // raised above, so be sure to call PyErr_WriteUnraisable first if + // necessary. + PyErr_SetRaisedException(exc); } + .. warning:: + + The :c:member:`~PyTypeObject.tp_finalize` finalizer function may or may + not be automatically called, depending on: + + * whether the garbage collector is enabled + * whether the object is a member of a :term:`cyclic isolate` + * whether the garbage collector has detected the cyclic isolate (yet) + * whether finalizing a different object in the same cyclic isolate + dropped the reference count to zero before the garbage collector would + have called the finalizer + * whether the finalizer has already been called + + To ensure that the finalizer is always called before an object is + destroyed, call :c:func:`PyObject_CallFinalizerFromDealloc` at the + beginning of :c:member:`~PyTypeObject.tp_dealloc`. (This does not + guarantee the finalizer will be called, only that it will be called if + :c:member:`!tp_dealloc` is called.) + + .. warning:: + + The :c:member:`!tp_finalize` function can be called from any thread. See + the documentation of the :meth:`~object.__del__` method for details. + + .. warning:: + + The :c:member:`!tp_finalize` function can be called during shutdown, + after some global variables have been deleted. See the documentation of + the :meth:`~object.__del__` method for details. + **Inheritance:** This field is inherited by subtypes. diff --git a/Doc/glossary.rst b/Doc/glossary.rst index f67f3ecad0bc40..c5a377f04081a8 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -333,6 +333,12 @@ Glossary tasks (see :mod:`asyncio`) associate each task with a context which becomes the current context whenever the task starts or resumes execution. + cyclic isolate + A subgroup of one or more usable, non-broken objects that reference each + other in a reference cycle, but are not referenced by objects outside the + group. The goal of the garbage collector is to identify these groups and + break the reference cycles so that the memory can be reclaimed. + decorator A function returning another function, usually applied as a function transformation using the ``@wrapper`` syntax. Common examples for From 361eaca4c88e68662a06e9bab010fda877adc017 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 25 Oct 2024 05:42:51 -0400 Subject: [PATCH 2/5] gh-75459: Doc: Tell sphinx to run graphviz --- .github/workflows/reusable-docs.yml | 6 + .readthedocs.yml | 43 +++--- Doc/c-api/lifecycle.dot | 96 ------------ Doc/c-api/lifecycle.dot.svg | 220 ---------------------------- Doc/c-api/lifecycle.rst | 98 ++++++++++++- Doc/conf.py | 6 + 6 files changed, 130 insertions(+), 339 deletions(-) delete mode 100644 Doc/c-api/lifecycle.dot delete mode 100644 Doc/c-api/lifecycle.dot.svg diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 39a97392e898aa..de1f4166c99a8a 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -80,6 +80,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: + - name: 'Install Dependencies' + run: | + sudo apt-get update && + sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + graphviz \ + ; - uses: actions/checkout@v4 - name: 'Set up Python' uses: actions/setup-python@v5 diff --git a/.readthedocs.yml b/.readthedocs.yml index a57de00544e4e3..c2b9fcc43007d3 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,25 +11,26 @@ build: os: ubuntu-24.04 tools: python: "3" + apt_packages: + - graphviz + jobs: + post_checkout: + # https://docs.readthedocs.io/en/stable/build-customization.html#cancel-build-based-on-a-condition + # + # Cancel building pull requests when there aren't changes in the Doc directory. + # + # If there are no changes (git diff exits with 0) we force the command to return with 183. + # This is a special exit code on Read the Docs that will cancel the build immediately. + - | + if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && [ "$(git diff --quiet origin/main -- Doc/ .readthedocs.yml; echo $?)" -eq 0 ]; + then + echo "No changes to Doc/ - exiting the build."; + exit 183; + fi - commands: - # https://docs.readthedocs.io/en/stable/build-customization.html#cancel-build-based-on-a-condition - # - # Cancel building pull requests when there aren't changes in the Doc directory. - # - # If there are no changes (git diff exits with 0) we force the command to return with 183. - # This is a special exit code on Read the Docs that will cancel the build immediately. - - | - if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && [ "$(git diff --quiet origin/main -- Doc/ .readthedocs.yml; echo $?)" -eq 0 ]; - then - echo "No changes to Doc/ - exiting the build."; - exit 183; - fi - - - asdf plugin add uv - - asdf install uv latest - - asdf global uv latest - - make -C Doc venv html - - mkdir _readthedocs - - mv Doc/build/html _readthedocs/html - + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - make -C Doc venv html + - mkdir _readthedocs + - mv Doc/build/html _readthedocs/html diff --git a/Doc/c-api/lifecycle.dot b/Doc/c-api/lifecycle.dot deleted file mode 100644 index 0c508f8361df58..00000000000000 --- a/Doc/c-api/lifecycle.dot +++ /dev/null @@ -1,96 +0,0 @@ -digraph G { - graph [ - fontname="svg" - fontsize=10.0 - layout="dot" - ranksep=0.25 - ] - node [ - fontname="Courier" - fontsize=10.0 - ] - edge [ - fontname="Times-Italic" - fontsize=10.0 - ] - - "start" [fontname="Times-Italic" shape=plain label=< start > style=invis] - "tp_alloc" [href="typeobj.html#c.PyTypeObject.tp_alloc" target="_top"] - "tp_new" [href="typeobj.html#c.PyTypeObject.tp_new" target="_top"] - "tp_init" [href="typeobj.html#c.PyTypeObject.tp_init" target="_top"] - { - rank="same" - "alive" [ - fontname="Times-Italic" - label= - shape=box - ] - "tp_traverse" [ - href="typeobj.html#c.PyTypeObject.tp_traverse" - shape=octagon - target="_top" - ] - } - "tp_finalize" [ - href="typeobj.html#c.PyTypeObject.tp_finalize" - shape=octagon - target="_top" - ] - "tp_clear" [ - href="typeobj.html#c.PyTypeObject.tp_clear" - shape=octagon - target="_top" - ] - "ref0" [ - fontname="Times-Italic" - label= - ordering="in" - shape=box - ] - "tp_dealloc" [href="typeobj.html#c.PyTypeObject.tp_dealloc" target="_top"] - "tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"] - - "start" -> "tp_alloc" - "tp_alloc" -> "tp_new" - "tp_new" -> "tp_init" - "tp_init" -> "alive" - "tp_traverse" -> "alive" - "alive" -> "tp_traverse" - "alive" -> "tp_clear" [label=< cyclic
isolate >] - "alive" -> "tp_finalize" [ - dir="back" - label=< resurrected > - ] - "alive" -> "tp_finalize" [label=< cyclic
isolate >] - "tp_finalize" -> "tp_clear" - "tp_finalize" -> "ref0" - "tp_clear" -> "ref0" - "tp_clear" -> "tp_dealloc" [ - dir="back" - label=< optional
direct call > - ] - "alive" -> "ref0" - "ref0" -> "tp_dealloc" - "tp_finalize" -> "tp_dealloc" [ - dir="back" - href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" - label=< - - - - - - - - - - - - - -
optional call to
PyObject_Call
FinalizerFrom
Dealloc
- > - target="_top" - ] - "tp_dealloc" -> "tp_free" [label=< directly calls >] -} diff --git a/Doc/c-api/lifecycle.dot.svg b/Doc/c-api/lifecycle.dot.svg deleted file mode 100644 index d97ae0d0e445ce..00000000000000 --- a/Doc/c-api/lifecycle.dot.svg +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - -G - - - - -tp_alloc - - -tp_alloc - - - - - -start->tp_alloc - - - - - -tp_new - - -tp_new - - - - - -tp_alloc->tp_new - - - - - -tp_init - - -tp_init - - - - - -tp_new->tp_init - - - - - -alive - -alive, ref count > 0 - - - -tp_init->alive - - - - - -tp_traverse - - -tp_traverse - - - - - -alive->tp_traverse - - - - - -tp_finalize - - -tp_finalize - - - - - -alive->tp_finalize - - -  resurrected   - - - -alive->tp_finalize - - -  cyclic     -isolate     - - - -tp_clear - - -tp_clear - - - - - -alive->tp_clear - - -  cyclic     -isolate     - - - -ref0 - -ref count == 0 - - - -alive->ref0 - - - - - -tp_traverse->alive - - - - - -tp_finalize->tp_clear - - - - - -tp_finalize->ref0 - - - - - -tp_dealloc - - -tp_dealloc - - - - - -tp_finalize->tp_dealloc - - - - - - - -optional call to -       -PyObject_Call -FinalizerFrom -Dealloc - - - - - -tp_clear->ref0 - - - - - -tp_clear->tp_dealloc - - -  optional -direct call   - - - -ref0->tp_dealloc - - - - - -tp_free - - -tp_free - - - - - -tp_dealloc->tp_free - - -  directly calls   - - - diff --git a/Doc/c-api/lifecycle.rst b/Doc/c-api/lifecycle.rst index 56f1a386f0760e..d4091267f795f3 100644 --- a/Doc/c-api/lifecycle.rst +++ b/Doc/c-api/lifecycle.rst @@ -12,8 +12,102 @@ The following is an illustration of the stages of life of an object. Arrows indicate a "happens before" relationship. Octagons indicate functions specific to :ref:`garbage collection support `. -.. raw:: html - :file: lifecycle.dot.svg +.. digraph:: callorder + + graph [ + fontname="svg" + fontsize=10.0 + layout="dot" + ranksep=0.25 + ] + node [ + fontname="Courier" + fontsize=10.0 + ] + edge [ + fontname="Times-Italic" + fontsize=10.0 + ] + + "start" [fontname="Times-Italic" shape=plain label=< start > style=invis] + "tp_alloc" [href="typeobj.html#c.PyTypeObject.tp_alloc" target="_top"] + "tp_new" [href="typeobj.html#c.PyTypeObject.tp_new" target="_top"] + "tp_init" [href="typeobj.html#c.PyTypeObject.tp_init" target="_top"] + { + rank="same" + "alive" [ + fontname="Times-Italic" + label= + shape=box + ] + "tp_traverse" [ + href="typeobj.html#c.PyTypeObject.tp_traverse" + shape=octagon + target="_top" + ] + } + "tp_finalize" [ + href="typeobj.html#c.PyTypeObject.tp_finalize" + shape=octagon + target="_top" + ] + "tp_clear" [ + href="typeobj.html#c.PyTypeObject.tp_clear" + shape=octagon + target="_top" + ] + "ref0" [ + fontname="Times-Italic" + label= + ordering="in" + shape=box + ] + "tp_dealloc" [href="typeobj.html#c.PyTypeObject.tp_dealloc" target="_top"] + "tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"] + + "start" -> "tp_alloc" + "tp_alloc" -> "tp_new" + "tp_new" -> "tp_init" + "tp_init" -> "alive" + "tp_traverse" -> "alive" + "alive" -> "tp_traverse" + "alive" -> "tp_clear" [label=< cyclic
isolate >] + "alive" -> "tp_finalize" [ + dir="back" + label=< resurrected > + ] + "alive" -> "tp_finalize" [label=< cyclic
isolate >] + "tp_finalize" -> "tp_clear" + "tp_finalize" -> "ref0" + "tp_clear" -> "ref0" + "tp_clear" -> "tp_dealloc" [ + dir="back" + label=< optional
direct call > + ] + "alive" -> "ref0" + "ref0" -> "tp_dealloc" + "tp_finalize" -> "tp_dealloc" [ + dir="back" + href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" + label=< + + + + + + + + + + + + + +
optional call to
PyObject_Call
FinalizerFrom
Dealloc
+ > + target="_top" + ] + "tp_dealloc" -> "tp_free" [label=< directly calls >] Explanation: diff --git a/Doc/conf.py b/Doc/conf.py index 7ee3c91581345d..3d9dbbadacc954 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -31,6 +31,7 @@ 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', + 'sphinx.ext.graphviz', ] # Skip if downstream redistributors haven't installed them @@ -639,3 +640,8 @@ '', '', ] + +# Options for sphinx.ext.graphviz +# ------------------------------- + +graphviz_output_format = 'svg' From b27bccaf8deba433d6428257d5e97de8d1faa5e6 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 25 Oct 2024 06:00:37 -0400 Subject: [PATCH 3/5] add blurb --- .../Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst diff --git a/Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst b/Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst new file mode 100644 index 00000000000000..266c7c66088194 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst @@ -0,0 +1,2 @@ +`Graphviz `_ is now required to build the +documentation. From b42b58d59be267f5b8e25529c18ccb5ab15b3054 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 25 Oct 2024 17:36:11 -0400 Subject: [PATCH 4/5] Revert "gh-75459: Doc: Tell sphinx to run graphviz" This reverts commit 361eaca4c88e68662a06e9bab010fda877adc017. --- .github/workflows/reusable-docs.yml | 6 - .readthedocs.yml | 43 ++-- Doc/c-api/lifecycle.dot | 96 ++++++++ Doc/c-api/lifecycle.dot.svg | 220 ++++++++++++++++++ Doc/c-api/lifecycle.rst | 98 +------- Doc/conf.py | 6 - ...4-10-25-06-00-21.gh-issue-75459.tiyJn2.rst | 2 - 7 files changed, 339 insertions(+), 132 deletions(-) create mode 100644 Doc/c-api/lifecycle.dot create mode 100644 Doc/c-api/lifecycle.dot.svg delete mode 100644 Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index de1f4166c99a8a..39a97392e898aa 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -80,12 +80,6 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - name: 'Install Dependencies' - run: | - sudo apt-get update && - sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - graphviz \ - ; - uses: actions/checkout@v4 - name: 'Set up Python' uses: actions/setup-python@v5 diff --git a/.readthedocs.yml b/.readthedocs.yml index c2b9fcc43007d3..a57de00544e4e3 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,26 +11,25 @@ build: os: ubuntu-24.04 tools: python: "3" - apt_packages: - - graphviz - jobs: - post_checkout: - # https://docs.readthedocs.io/en/stable/build-customization.html#cancel-build-based-on-a-condition - # - # Cancel building pull requests when there aren't changes in the Doc directory. - # - # If there are no changes (git diff exits with 0) we force the command to return with 183. - # This is a special exit code on Read the Docs that will cancel the build immediately. - - | - if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && [ "$(git diff --quiet origin/main -- Doc/ .readthedocs.yml; echo $?)" -eq 0 ]; - then - echo "No changes to Doc/ - exiting the build."; - exit 183; - fi - - asdf plugin add uv - - asdf install uv latest - - asdf global uv latest - - make -C Doc venv html - - mkdir _readthedocs - - mv Doc/build/html _readthedocs/html + commands: + # https://docs.readthedocs.io/en/stable/build-customization.html#cancel-build-based-on-a-condition + # + # Cancel building pull requests when there aren't changes in the Doc directory. + # + # If there are no changes (git diff exits with 0) we force the command to return with 183. + # This is a special exit code on Read the Docs that will cancel the build immediately. + - | + if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && [ "$(git diff --quiet origin/main -- Doc/ .readthedocs.yml; echo $?)" -eq 0 ]; + then + echo "No changes to Doc/ - exiting the build."; + exit 183; + fi + + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - make -C Doc venv html + - mkdir _readthedocs + - mv Doc/build/html _readthedocs/html + diff --git a/Doc/c-api/lifecycle.dot b/Doc/c-api/lifecycle.dot new file mode 100644 index 00000000000000..0c508f8361df58 --- /dev/null +++ b/Doc/c-api/lifecycle.dot @@ -0,0 +1,96 @@ +digraph G { + graph [ + fontname="svg" + fontsize=10.0 + layout="dot" + ranksep=0.25 + ] + node [ + fontname="Courier" + fontsize=10.0 + ] + edge [ + fontname="Times-Italic" + fontsize=10.0 + ] + + "start" [fontname="Times-Italic" shape=plain label=< start > style=invis] + "tp_alloc" [href="typeobj.html#c.PyTypeObject.tp_alloc" target="_top"] + "tp_new" [href="typeobj.html#c.PyTypeObject.tp_new" target="_top"] + "tp_init" [href="typeobj.html#c.PyTypeObject.tp_init" target="_top"] + { + rank="same" + "alive" [ + fontname="Times-Italic" + label= + shape=box + ] + "tp_traverse" [ + href="typeobj.html#c.PyTypeObject.tp_traverse" + shape=octagon + target="_top" + ] + } + "tp_finalize" [ + href="typeobj.html#c.PyTypeObject.tp_finalize" + shape=octagon + target="_top" + ] + "tp_clear" [ + href="typeobj.html#c.PyTypeObject.tp_clear" + shape=octagon + target="_top" + ] + "ref0" [ + fontname="Times-Italic" + label= + ordering="in" + shape=box + ] + "tp_dealloc" [href="typeobj.html#c.PyTypeObject.tp_dealloc" target="_top"] + "tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"] + + "start" -> "tp_alloc" + "tp_alloc" -> "tp_new" + "tp_new" -> "tp_init" + "tp_init" -> "alive" + "tp_traverse" -> "alive" + "alive" -> "tp_traverse" + "alive" -> "tp_clear" [label=< cyclic
isolate >] + "alive" -> "tp_finalize" [ + dir="back" + label=< resurrected > + ] + "alive" -> "tp_finalize" [label=< cyclic
isolate >] + "tp_finalize" -> "tp_clear" + "tp_finalize" -> "ref0" + "tp_clear" -> "ref0" + "tp_clear" -> "tp_dealloc" [ + dir="back" + label=< optional
direct call > + ] + "alive" -> "ref0" + "ref0" -> "tp_dealloc" + "tp_finalize" -> "tp_dealloc" [ + dir="back" + href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" + label=< + + + + + + + + + + + + + +
optional call to
PyObject_Call
FinalizerFrom
Dealloc
+ > + target="_top" + ] + "tp_dealloc" -> "tp_free" [label=< directly calls >] +} diff --git a/Doc/c-api/lifecycle.dot.svg b/Doc/c-api/lifecycle.dot.svg new file mode 100644 index 00000000000000..d97ae0d0e445ce --- /dev/null +++ b/Doc/c-api/lifecycle.dot.svg @@ -0,0 +1,220 @@ + + + + + + +G + + + + +tp_alloc + + +tp_alloc + + + + + +start->tp_alloc + + + + + +tp_new + + +tp_new + + + + + +tp_alloc->tp_new + + + + + +tp_init + + +tp_init + + + + + +tp_new->tp_init + + + + + +alive + +alive, ref count > 0 + + + +tp_init->alive + + + + + +tp_traverse + + +tp_traverse + + + + + +alive->tp_traverse + + + + + +tp_finalize + + +tp_finalize + + + + + +alive->tp_finalize + + +  resurrected   + + + +alive->tp_finalize + + +  cyclic     +isolate     + + + +tp_clear + + +tp_clear + + + + + +alive->tp_clear + + +  cyclic     +isolate     + + + +ref0 + +ref count == 0 + + + +alive->ref0 + + + + + +tp_traverse->alive + + + + + +tp_finalize->tp_clear + + + + + +tp_finalize->ref0 + + + + + +tp_dealloc + + +tp_dealloc + + + + + +tp_finalize->tp_dealloc + + + + + + + +optional call to +       +PyObject_Call +FinalizerFrom +Dealloc + + + + + +tp_clear->ref0 + + + + + +tp_clear->tp_dealloc + + +  optional +direct call   + + + +ref0->tp_dealloc + + + + + +tp_free + + +tp_free + + + + + +tp_dealloc->tp_free + + +  directly calls   + + + diff --git a/Doc/c-api/lifecycle.rst b/Doc/c-api/lifecycle.rst index d4091267f795f3..56f1a386f0760e 100644 --- a/Doc/c-api/lifecycle.rst +++ b/Doc/c-api/lifecycle.rst @@ -12,102 +12,8 @@ The following is an illustration of the stages of life of an object. Arrows indicate a "happens before" relationship. Octagons indicate functions specific to :ref:`garbage collection support `. -.. digraph:: callorder - - graph [ - fontname="svg" - fontsize=10.0 - layout="dot" - ranksep=0.25 - ] - node [ - fontname="Courier" - fontsize=10.0 - ] - edge [ - fontname="Times-Italic" - fontsize=10.0 - ] - - "start" [fontname="Times-Italic" shape=plain label=< start > style=invis] - "tp_alloc" [href="typeobj.html#c.PyTypeObject.tp_alloc" target="_top"] - "tp_new" [href="typeobj.html#c.PyTypeObject.tp_new" target="_top"] - "tp_init" [href="typeobj.html#c.PyTypeObject.tp_init" target="_top"] - { - rank="same" - "alive" [ - fontname="Times-Italic" - label= - shape=box - ] - "tp_traverse" [ - href="typeobj.html#c.PyTypeObject.tp_traverse" - shape=octagon - target="_top" - ] - } - "tp_finalize" [ - href="typeobj.html#c.PyTypeObject.tp_finalize" - shape=octagon - target="_top" - ] - "tp_clear" [ - href="typeobj.html#c.PyTypeObject.tp_clear" - shape=octagon - target="_top" - ] - "ref0" [ - fontname="Times-Italic" - label= - ordering="in" - shape=box - ] - "tp_dealloc" [href="typeobj.html#c.PyTypeObject.tp_dealloc" target="_top"] - "tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"] - - "start" -> "tp_alloc" - "tp_alloc" -> "tp_new" - "tp_new" -> "tp_init" - "tp_init" -> "alive" - "tp_traverse" -> "alive" - "alive" -> "tp_traverse" - "alive" -> "tp_clear" [label=< cyclic
isolate >] - "alive" -> "tp_finalize" [ - dir="back" - label=< resurrected > - ] - "alive" -> "tp_finalize" [label=< cyclic
isolate >] - "tp_finalize" -> "tp_clear" - "tp_finalize" -> "ref0" - "tp_clear" -> "ref0" - "tp_clear" -> "tp_dealloc" [ - dir="back" - label=< optional
direct call > - ] - "alive" -> "ref0" - "ref0" -> "tp_dealloc" - "tp_finalize" -> "tp_dealloc" [ - dir="back" - href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" - label=< - - - - - - - - - - - - - -
optional call to
PyObject_Call
FinalizerFrom
Dealloc
- > - target="_top" - ] - "tp_dealloc" -> "tp_free" [label=< directly calls >] +.. raw:: html + :file: lifecycle.dot.svg Explanation: diff --git a/Doc/conf.py b/Doc/conf.py index 3d9dbbadacc954..7ee3c91581345d 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -31,7 +31,6 @@ 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', - 'sphinx.ext.graphviz', ] # Skip if downstream redistributors haven't installed them @@ -640,8 +639,3 @@ '', '', ] - -# Options for sphinx.ext.graphviz -# ------------------------------- - -graphviz_output_format = 'svg' diff --git a/Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst b/Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst deleted file mode 100644 index 266c7c66088194..00000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-10-25-06-00-21.gh-issue-75459.tiyJn2.rst +++ /dev/null @@ -1,2 +0,0 @@ -`Graphviz `_ is now required to build the -documentation. From 7e571ae3a8394d67aac0d456868fd885c840f211 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sat, 26 Oct 2024 03:31:33 -0400 Subject: [PATCH 5/5] delete "ref count == 0" node, link directly to `tp_dealloc` --- Doc/c-api/lifecycle.dot | 23 ++--- Doc/c-api/lifecycle.dot.svg | 192 +++++++++++++++++------------------- 2 files changed, 103 insertions(+), 112 deletions(-) diff --git a/Doc/c-api/lifecycle.dot b/Doc/c-api/lifecycle.dot index 0c508f8361df58..6282ad3b93f21d 100644 --- a/Doc/c-api/lifecycle.dot +++ b/Doc/c-api/lifecycle.dot @@ -41,13 +41,11 @@ digraph G { shape=octagon target="_top" ] - "ref0" [ - fontname="Times-Italic" - label= + "tp_dealloc" [ + href="typeobj.html#c.PyTypeObject.tp_dealloc" ordering="in" - shape=box + target="_top" ] - "tp_dealloc" [href="typeobj.html#c.PyTypeObject.tp_dealloc" target="_top"] "tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"] "start" -> "tp_alloc" @@ -63,14 +61,6 @@ digraph G { ] "alive" -> "tp_finalize" [label=< cyclic
isolate >] "tp_finalize" -> "tp_clear" - "tp_finalize" -> "ref0" - "tp_clear" -> "ref0" - "tp_clear" -> "tp_dealloc" [ - dir="back" - label=< optional
direct call > - ] - "alive" -> "ref0" - "ref0" -> "tp_dealloc" "tp_finalize" -> "tp_dealloc" [ dir="back" href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc" @@ -92,5 +82,12 @@ digraph G { > target="_top" ] + "tp_finalize" -> "tp_dealloc" [label=< ref count
== 0 >] + "tp_clear" -> "tp_dealloc" [label=< ref count
== 0 >] + "tp_clear" -> "tp_dealloc" [ + dir="back" + label=< optional
direct call > + ] + "alive" -> "tp_dealloc" [label=< ref count
== 0 >] "tp_dealloc" -> "tp_free" [label=< directly calls >] } diff --git a/Doc/c-api/lifecycle.dot.svg b/Doc/c-api/lifecycle.dot.svg index d97ae0d0e445ce..ba0fdc49d06cb5 100644 --- a/Doc/c-api/lifecycle.dot.svg +++ b/Doc/c-api/lifecycle.dot.svg @@ -4,217 +4,211 @@ - - + + G - + tp_alloc - -tp_alloc + +tp_alloc start->tp_alloc - - + + tp_new - -tp_new + +tp_new tp_alloc->tp_new - - + + tp_init - -tp_init + +tp_init tp_new->tp_init - - + + alive - -alive, ref count > 0 + +alive, ref count > 0 tp_init->alive - - + + tp_traverse - -tp_traverse + +tp_traverse alive->tp_traverse - - + + tp_finalize - -tp_finalize + +tp_finalize alive->tp_finalize - - -  resurrected   + + +  resurrected   alive->tp_finalize - - -  cyclic     -isolate     + + +  cyclic     +isolate     tp_clear - -tp_clear + +tp_clear alive->tp_clear - - -  cyclic     -isolate     + + +  cyclic     +isolate     - + -ref0 - -ref count == 0 +tp_dealloc + + +tp_dealloc + - - -alive->ref0 - - + + + +alive->tp_dealloc + + +  ref count   +  == 0   tp_traverse->alive - - + + tp_finalize->tp_clear - - - - - -tp_finalize->ref0 - - - - - -tp_dealloc - - -tp_dealloc - - + + - + tp_finalize->tp_dealloc - - - + + + - - -optional call to -       -PyObject_Call -FinalizerFrom -Dealloc + + +optional call to +       +PyObject_Call +FinalizerFrom +Dealloc - + -tp_clear->ref0 - - +tp_finalize->tp_dealloc + + +  ref count   +  == 0   tp_clear->tp_dealloc - - -  optional -direct call   + + +  ref count   +  == 0   - - -ref0->tp_dealloc - - + + +tp_clear->tp_dealloc + + +  optional +direct call   - + tp_free - - -tp_free + + +tp_free - + tp_dealloc->tp_free - - -  directly calls   + + +  directly calls