mirror of https://github.com/python/cpython
Issue #18112: PEP 442 implementation (safe object finalization).
This commit is contained in:
parent
c5d95b17ac
commit
796564c27b
|
@ -465,6 +465,14 @@ type objects) *must* have the :attr:`ob_size` field.
|
||||||
:const:`Py_TPFLAGS_HAVE_VERSION_TAG`.
|
:const:`Py_TPFLAGS_HAVE_VERSION_TAG`.
|
||||||
|
|
||||||
|
|
||||||
|
.. data:: Py_TPFLAGS_HAVE_FINALIZE
|
||||||
|
|
||||||
|
This bit is set when the :attr:`tp_finalize` slot is present in the
|
||||||
|
type structure.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
|
||||||
.. c:member:: char* PyTypeObject.tp_doc
|
.. c:member:: char* PyTypeObject.tp_doc
|
||||||
|
|
||||||
An optional pointer to a NUL-terminated C string giving the docstring for this
|
An optional pointer to a NUL-terminated C string giving the docstring for this
|
||||||
|
@ -968,6 +976,47 @@ type objects) *must* have the :attr:`ob_size` field.
|
||||||
This field is not inherited; it is calculated fresh by :c:func:`PyType_Ready`.
|
This field is not inherited; it is calculated fresh by :c:func:`PyType_Ready`.
|
||||||
|
|
||||||
|
|
||||||
|
.. c:member:: destructor PyTypeObject.tp_finalize
|
||||||
|
|
||||||
|
An optional pointer to an instance finalization function. Its signature is
|
||||||
|
:c:type:`destructor`::
|
||||||
|
|
||||||
|
void tp_finalize(PyObject *)
|
||||||
|
|
||||||
|
If :attr:`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.
|
||||||
|
|
||||||
|
:attr:`tp_finalize` should not mutate the current exception status;
|
||||||
|
therefore, a recommended way to write a non-trivial finalizer is::
|
||||||
|
|
||||||
|
static void
|
||||||
|
local_finalize(PyObject *self)
|
||||||
|
{
|
||||||
|
PyObject *error_type, *error_value, *error_traceback;
|
||||||
|
|
||||||
|
/* Save the current exception, if any. */
|
||||||
|
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||||
|
|
||||||
|
/* ... */
|
||||||
|
|
||||||
|
/* Restore the saved exception. */
|
||||||
|
PyErr_Restore(error_type, error_value, error_traceback);
|
||||||
|
}
|
||||||
|
|
||||||
|
For this field to be taken into account (even through inheritance),
|
||||||
|
you must also set the :const:`Py_TPFLAGS_HAVE_FINALIZE` flags bit.
|
||||||
|
|
||||||
|
This field is inherited by subtypes.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4
|
||||||
|
|
||||||
|
.. seealso:: "Safe object finalization" (:pep:`442`)
|
||||||
|
|
||||||
|
|
||||||
.. c:member:: PyObject* PyTypeObject.tp_cache
|
.. c:member:: PyObject* PyTypeObject.tp_cache
|
||||||
|
|
||||||
Unused. Not inherited. Internal use only.
|
Unused. Not inherited. Internal use only.
|
||||||
|
|
|
@ -157,7 +157,8 @@ to :const:`Py_TPFLAGS_DEFAULT`. ::
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||||
|
|
||||||
All types should include this constant in their flags. It enables all of the
|
All types should include this constant in their flags. It enables all of the
|
||||||
members defined by the current version of Python.
|
members defined until at least Python 3.3. If you need further members,
|
||||||
|
you will need to OR the corresponding flags.
|
||||||
|
|
||||||
We provide a doc string for the type in :attr:`tp_doc`. ::
|
We provide a doc string for the type in :attr:`tp_doc`. ::
|
||||||
|
|
||||||
|
@ -928,8 +929,9 @@ Finalization and De-allocation
|
||||||
|
|
||||||
This function is called when the reference count of the instance of your type is
|
This function is called when the reference count of the instance of your type is
|
||||||
reduced to zero and the Python interpreter wants to reclaim it. If your type
|
reduced to zero and the Python interpreter wants to reclaim it. If your type
|
||||||
has memory to free or other clean-up to perform, put it here. The object itself
|
has memory to free or other clean-up to perform, you can put it here. The
|
||||||
needs to be freed here as well. Here is an example of this function::
|
object itself needs to be freed here as well. Here is an example of this
|
||||||
|
function::
|
||||||
|
|
||||||
static void
|
static void
|
||||||
newdatatype_dealloc(newdatatypeobject * obj)
|
newdatatype_dealloc(newdatatypeobject * obj)
|
||||||
|
@ -981,6 +983,22 @@ done. This can be done using the :c:func:`PyErr_Fetch` and
|
||||||
Py_TYPE(obj)->tp_free((PyObject*)self);
|
Py_TYPE(obj)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
There are limitations to what you can safely do in a deallocator function.
|
||||||
|
First, if your type supports garbage collection (using :attr:`tp_traverse`
|
||||||
|
and/or :attr:`tp_clear`), some of the object's members can have been
|
||||||
|
cleared or finalized by the time :attr:`tp_dealloc` is called. Second, in
|
||||||
|
:attr:`tp_dealloc`, your object is in an unstable state: its reference
|
||||||
|
count is equal to zero. Any call to a non-trivial object or API (as in the
|
||||||
|
example above) might end up calling :attr:`tp_dealloc` again, causing a
|
||||||
|
double free and a crash.
|
||||||
|
|
||||||
|
Starting with Python 3.4, it is recommended not to put any complex
|
||||||
|
finalization code in :attr:`tp_dealloc`, and instead use the new
|
||||||
|
:c:member:`~PyTypeObject.tp_finalize` type method.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
:pep:`442` explains the new finalization scheme.
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: string; object representation
|
single: string; object representation
|
||||||
|
|
|
@ -70,4 +70,11 @@ typedef struct _typeobject {
|
||||||
PyObject *tp_subclasses;
|
PyObject *tp_subclasses;
|
||||||
PyObject *tp_weaklist;
|
PyObject *tp_weaklist;
|
||||||
|
|
||||||
|
destructor tp_del;
|
||||||
|
|
||||||
|
/* Type attribute cache version tag. Added in version 2.6 */
|
||||||
|
unsigned int tp_version_tag;
|
||||||
|
|
||||||
|
destructor tp_finalize;
|
||||||
|
|
||||||
} PyTypeObject;
|
} PyTypeObject;
|
||||||
|
|
|
@ -176,24 +176,13 @@ values but should not rebind them):
|
||||||
|
|
||||||
.. data:: garbage
|
.. data:: garbage
|
||||||
|
|
||||||
A list of objects which the collector found to be unreachable but could not be
|
A list of objects which the collector found to be unreachable but could
|
||||||
freed (uncollectable objects). By default, this list contains only objects with
|
not be freed (uncollectable objects). Starting with Python 3.4, this
|
||||||
:meth:`__del__` methods. Objects that have :meth:`__del__` methods and are
|
list should be empty most of the time, except when using instances of
|
||||||
part of a reference cycle cause the entire reference cycle to be uncollectable,
|
C extension types with a non-NULL ``tp_del`` slot.
|
||||||
including objects not necessarily in the cycle but reachable only from it.
|
|
||||||
Python doesn't collect such cycles automatically because, in general, it isn't
|
|
||||||
possible for Python to guess a safe order in which to run the :meth:`__del__`
|
|
||||||
methods. If you know a safe order, you can force the issue by examining the
|
|
||||||
*garbage* list, and explicitly breaking cycles due to your objects within the
|
|
||||||
list. Note that these objects are kept alive even so by virtue of being in the
|
|
||||||
*garbage* list, so they should be removed from *garbage* too. For example,
|
|
||||||
after breaking cycles, do ``del gc.garbage[:]`` to empty the list. It's
|
|
||||||
generally better to avoid the issue by not creating cycles containing objects
|
|
||||||
with :meth:`__del__` methods, and *garbage* can be examined in that case to
|
|
||||||
verify that no such cycles are being created.
|
|
||||||
|
|
||||||
If :const:`DEBUG_SAVEALL` is set, then all unreachable objects will be added
|
If :const:`DEBUG_SAVEALL` is set, then all unreachable objects will be
|
||||||
to this list rather than freed.
|
added to this list rather than freed.
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
If this list is non-empty at interpreter shutdown, a
|
If this list is non-empty at interpreter shutdown, a
|
||||||
|
@ -201,6 +190,10 @@ values but should not rebind them):
|
||||||
:const:`DEBUG_UNCOLLECTABLE` is set, in addition all uncollectable objects
|
:const:`DEBUG_UNCOLLECTABLE` is set, in addition all uncollectable objects
|
||||||
are printed.
|
are printed.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.4
|
||||||
|
Following :pep:`442`, objects with a :meth:`__del__` method don't end
|
||||||
|
up in :attr:`gc.garbage` anymore.
|
||||||
|
|
||||||
.. data:: callbacks
|
.. data:: callbacks
|
||||||
|
|
||||||
A list of callbacks that will be invoked by the garbage collector before and
|
A list of callbacks that will be invoked by the garbage collector before and
|
||||||
|
|
|
@ -529,22 +529,13 @@ follows::
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.remove()
|
self.remove()
|
||||||
|
|
||||||
This solution has a couple of serious problems:
|
This solution has a serious problem: the :meth:`__del__` method may be
|
||||||
|
called at shutdown after the :mod:`shutil` module has been cleaned up,
|
||||||
|
in which case :attr:`shutil.rmtree` will have been replaced by :const:`None`.
|
||||||
|
This will cause the :meth:`__del__` method to fail and the directory
|
||||||
|
will not be removed.
|
||||||
|
|
||||||
* There is no guarantee that the object will be garbage collected
|
Using finalizers we can avoid this problem::
|
||||||
before the program exists, so the directory might be left. This is
|
|
||||||
because reference cycles containing an object with a :meth:`__del__`
|
|
||||||
method can never be collected. And even if the :class:`TempDir`
|
|
||||||
object is not itself part of a reference cycle, it may still be kept
|
|
||||||
alive by some unkown uncollectable reference cycle.
|
|
||||||
|
|
||||||
* The :meth:`__del__` method may be called at shutdown after the
|
|
||||||
:mod:`shutil` module has been cleaned up, in which case
|
|
||||||
:attr:`shutil.rmtree` will have been replaced by :const:`None`.
|
|
||||||
This will cause the :meth:`__del__` method to fail and the directory
|
|
||||||
will not be removed.
|
|
||||||
|
|
||||||
Using finalizers we can avoid these problems::
|
|
||||||
|
|
||||||
class TempDir:
|
class TempDir:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -1120,12 +1120,10 @@ Basic customization
|
||||||
``sys.last_traceback`` keeps the stack frame alive). The first situation
|
``sys.last_traceback`` keeps the stack frame alive). The first situation
|
||||||
can only be remedied by explicitly breaking the cycles; the latter two
|
can only be remedied by explicitly breaking the cycles; the latter two
|
||||||
situations can be resolved by storing ``None`` in ``sys.last_traceback``.
|
situations can be resolved by storing ``None`` in ``sys.last_traceback``.
|
||||||
Circular references which are garbage are detected when the option cycle
|
Circular references which are garbage are detected and cleaned up when
|
||||||
detector is enabled (it's on by default), but can only be cleaned up if
|
the cyclic garbage collector is enabled (it's on by default). Refer to the
|
||||||
there are no Python- level :meth:`__del__` methods involved. Refer to the
|
documentation for the :mod:`gc` module for more information about this
|
||||||
documentation for the :mod:`gc` module for more information about how
|
topic.
|
||||||
:meth:`__del__` methods are handled by the cycle detector, particularly
|
|
||||||
the description of the ``garbage`` value.
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,21 @@ The :pep:`445` adds new Application Programming Interfaces (API) to customize
|
||||||
Python memory allocators.
|
Python memory allocators.
|
||||||
|
|
||||||
|
|
||||||
|
.. _pep-442:
|
||||||
|
|
||||||
|
PEP 442: Safe object finalization
|
||||||
|
=================================
|
||||||
|
|
||||||
|
This PEP removes the current limitations and quirks of object finalization.
|
||||||
|
With it, objects with :meth:`__del__` methods, as well as generators
|
||||||
|
with :keyword:`finally` clauses, can be finalized when they are part of a
|
||||||
|
reference cycle.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:pep:`442` - Safe object finalization
|
||||||
|
PEP written and implemented by Antoine Pitrou
|
||||||
|
|
||||||
|
|
||||||
Other Language Changes
|
Other Language Changes
|
||||||
======================
|
======================
|
||||||
|
|
|
@ -408,6 +408,8 @@ typedef struct _typeobject {
|
||||||
/* Type attribute cache version tag. Added in version 2.6 */
|
/* Type attribute cache version tag. Added in version 2.6 */
|
||||||
unsigned int tp_version_tag;
|
unsigned int tp_version_tag;
|
||||||
|
|
||||||
|
destructor tp_finalize;
|
||||||
|
|
||||||
#ifdef COUNT_ALLOCS
|
#ifdef COUNT_ALLOCS
|
||||||
/* these must be last and never explicitly initialized */
|
/* these must be last and never explicitly initialized */
|
||||||
Py_ssize_t tp_allocs;
|
Py_ssize_t tp_allocs;
|
||||||
|
@ -529,6 +531,8 @@ PyAPI_FUNC(int) PyObject_Not(PyObject *);
|
||||||
PyAPI_FUNC(int) PyCallable_Check(PyObject *);
|
PyAPI_FUNC(int) PyCallable_Check(PyObject *);
|
||||||
|
|
||||||
PyAPI_FUNC(void) PyObject_ClearWeakRefs(PyObject *);
|
PyAPI_FUNC(void) PyObject_ClearWeakRefs(PyObject *);
|
||||||
|
PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *);
|
||||||
|
PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *);
|
||||||
|
|
||||||
/* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes
|
/* Same as PyObject_Generic{Get,Set}Attr, but passing the attributes
|
||||||
dict as the last parameter. */
|
dict as the last parameter. */
|
||||||
|
@ -646,6 +650,12 @@ given type object has a specified feature.
|
||||||
Py_TPFLAGS_HAVE_VERSION_TAG | \
|
Py_TPFLAGS_HAVE_VERSION_TAG | \
|
||||||
0)
|
0)
|
||||||
|
|
||||||
|
/* NOTE: The following flags reuse lower bits (removed as part of the
|
||||||
|
* Python 3.0 transition). */
|
||||||
|
|
||||||
|
/* Type structure has tp_finalize member (3.4) */
|
||||||
|
#define Py_TPFLAGS_HAVE_FINALIZE (1UL << 0)
|
||||||
|
|
||||||
#ifdef Py_LIMITED_API
|
#ifdef Py_LIMITED_API
|
||||||
#define PyType_HasFeature(t,f) ((PyType_GetFlags(t) & (f)) != 0)
|
#define PyType_HasFeature(t,f) ((PyType_GetFlags(t) & (f)) != 0)
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -256,6 +256,30 @@ extern PyGC_Head *_PyGC_generation0;
|
||||||
|
|
||||||
#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)
|
#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)
|
||||||
|
|
||||||
|
/* Bit 0 is set when tp_finalize is called */
|
||||||
|
#define _PyGC_REFS_MASK_FINALIZED (1 << 0)
|
||||||
|
/* The (N-1) most significant bits contain the gc state / refcount */
|
||||||
|
#define _PyGC_REFS_SHIFT (1)
|
||||||
|
#define _PyGC_REFS_MASK (((size_t) -1) << _PyGC_REFS_SHIFT)
|
||||||
|
|
||||||
|
#define _PyGCHead_REFS(g) ((g)->gc.gc_refs >> _PyGC_REFS_SHIFT)
|
||||||
|
#define _PyGCHead_SET_REFS(g, v) do { \
|
||||||
|
(g)->gc.gc_refs = ((g)->gc.gc_refs & ~_PyGC_REFS_MASK) \
|
||||||
|
| (v << _PyGC_REFS_SHIFT); \
|
||||||
|
} while (0)
|
||||||
|
#define _PyGCHead_DECREF(g) ((g)->gc.gc_refs -= 1 << _PyGC_REFS_SHIFT)
|
||||||
|
|
||||||
|
#define _PyGCHead_FINALIZED(g) (((g)->gc.gc_refs & _PyGC_REFS_MASK_FINALIZED) != 0)
|
||||||
|
#define _PyGCHead_SET_FINALIZED(g, v) do { \
|
||||||
|
(g)->gc.gc_refs = ((g)->gc.gc_refs & ~_PyGC_REFS_MASK_FINALIZED) \
|
||||||
|
| (v != 0); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define _PyGC_FINALIZED(o) _PyGCHead_FINALIZED(_Py_AS_GC(o))
|
||||||
|
#define _PyGC_SET_FINALIZED(o, v) _PyGCHead_SET_FINALIZED(_Py_AS_GC(o), v)
|
||||||
|
|
||||||
|
#define _PyGC_REFS(o) _PyGCHead_REFS(_Py_AS_GC(o))
|
||||||
|
|
||||||
#define _PyGC_REFS_UNTRACKED (-2)
|
#define _PyGC_REFS_UNTRACKED (-2)
|
||||||
#define _PyGC_REFS_REACHABLE (-3)
|
#define _PyGC_REFS_REACHABLE (-3)
|
||||||
#define _PyGC_REFS_TENTATIVELY_UNREACHABLE (-4)
|
#define _PyGC_REFS_TENTATIVELY_UNREACHABLE (-4)
|
||||||
|
@ -264,9 +288,9 @@ extern PyGC_Head *_PyGC_generation0;
|
||||||
* collector it must be safe to call the ob_traverse method. */
|
* collector it must be safe to call the ob_traverse method. */
|
||||||
#define _PyObject_GC_TRACK(o) do { \
|
#define _PyObject_GC_TRACK(o) do { \
|
||||||
PyGC_Head *g = _Py_AS_GC(o); \
|
PyGC_Head *g = _Py_AS_GC(o); \
|
||||||
if (g->gc.gc_refs != _PyGC_REFS_UNTRACKED) \
|
if (_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED) \
|
||||||
Py_FatalError("GC object already tracked"); \
|
Py_FatalError("GC object already tracked"); \
|
||||||
g->gc.gc_refs = _PyGC_REFS_REACHABLE; \
|
_PyGCHead_SET_REFS(g, _PyGC_REFS_REACHABLE); \
|
||||||
g->gc.gc_next = _PyGC_generation0; \
|
g->gc.gc_next = _PyGC_generation0; \
|
||||||
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \
|
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \
|
||||||
g->gc.gc_prev->gc.gc_next = g; \
|
g->gc.gc_prev->gc.gc_next = g; \
|
||||||
|
@ -279,8 +303,8 @@ extern PyGC_Head *_PyGC_generation0;
|
||||||
*/
|
*/
|
||||||
#define _PyObject_GC_UNTRACK(o) do { \
|
#define _PyObject_GC_UNTRACK(o) do { \
|
||||||
PyGC_Head *g = _Py_AS_GC(o); \
|
PyGC_Head *g = _Py_AS_GC(o); \
|
||||||
assert(g->gc.gc_refs != _PyGC_REFS_UNTRACKED); \
|
assert(_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED); \
|
||||||
g->gc.gc_refs = _PyGC_REFS_UNTRACKED; \
|
_PyGCHead_SET_REFS(g, _PyGC_REFS_UNTRACKED); \
|
||||||
g->gc.gc_prev->gc.gc_next = g->gc.gc_next; \
|
g->gc.gc_prev->gc.gc_next = g->gc.gc_next; \
|
||||||
g->gc.gc_next->gc.gc_prev = g->gc.gc_prev; \
|
g->gc.gc_next->gc.gc_prev = g->gc.gc_prev; \
|
||||||
g->gc.gc_next = NULL; \
|
g->gc.gc_next = NULL; \
|
||||||
|
@ -288,7 +312,7 @@ extern PyGC_Head *_PyGC_generation0;
|
||||||
|
|
||||||
/* True if the object is currently tracked by the GC. */
|
/* True if the object is currently tracked by the GC. */
|
||||||
#define _PyObject_GC_IS_TRACKED(o) \
|
#define _PyObject_GC_IS_TRACKED(o) \
|
||||||
((_Py_AS_GC(o))->gc.gc_refs != _PyGC_REFS_UNTRACKED)
|
(_PyGC_REFS(o) != _PyGC_REFS_UNTRACKED)
|
||||||
|
|
||||||
/* True if the object may be tracked by the GC in the future, or already is.
|
/* True if the object may be tracked by the GC in the future, or already is.
|
||||||
This can be useful to implement some optimizations. */
|
This can be useful to implement some optimizations. */
|
||||||
|
|
|
@ -3736,18 +3736,8 @@ order (MRO) for bases """
|
||||||
# bug).
|
# bug).
|
||||||
del c
|
del c
|
||||||
|
|
||||||
# If that didn't blow up, it's also interesting to see whether clearing
|
|
||||||
# the last container slot works: that will attempt to delete c again,
|
|
||||||
# which will cause c to get appended back to the container again
|
|
||||||
# "during" the del. (On non-CPython implementations, however, __del__
|
|
||||||
# is typically not called again.)
|
|
||||||
support.gc_collect()
|
support.gc_collect()
|
||||||
self.assertEqual(len(C.container), 1)
|
self.assertEqual(len(C.container), 1)
|
||||||
del C.container[-1]
|
|
||||||
if support.check_impl_detail():
|
|
||||||
support.gc_collect()
|
|
||||||
self.assertEqual(len(C.container), 1)
|
|
||||||
self.assertEqual(C.container[-1].attr, 42)
|
|
||||||
|
|
||||||
# Make c mortal again, so that the test framework with -l doesn't report
|
# Make c mortal again, so that the test framework with -l doesn't report
|
||||||
# it as a leak.
|
# it as a leak.
|
||||||
|
|
|
@ -0,0 +1,513 @@
|
||||||
|
"""
|
||||||
|
Tests for object finalization semantics, as outlined in PEP 442.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import gc
|
||||||
|
import unittest
|
||||||
|
import weakref
|
||||||
|
|
||||||
|
import _testcapi
|
||||||
|
from test import support
|
||||||
|
|
||||||
|
|
||||||
|
class NonGCSimpleBase:
|
||||||
|
"""
|
||||||
|
The base class for all the objects under test, equipped with various
|
||||||
|
testing features.
|
||||||
|
"""
|
||||||
|
|
||||||
|
survivors = []
|
||||||
|
del_calls = []
|
||||||
|
tp_del_calls = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
_cleaning = False
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _cleanup(cls):
|
||||||
|
cls.survivors.clear()
|
||||||
|
cls.errors.clear()
|
||||||
|
gc.garbage.clear()
|
||||||
|
gc.collect()
|
||||||
|
cls.del_calls.clear()
|
||||||
|
cls.tp_del_calls.clear()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def test(cls):
|
||||||
|
"""
|
||||||
|
A context manager to use around all finalization tests.
|
||||||
|
"""
|
||||||
|
with support.disable_gc():
|
||||||
|
cls.del_calls.clear()
|
||||||
|
cls.tp_del_calls.clear()
|
||||||
|
NonGCSimpleBase._cleaning = False
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
if cls.errors:
|
||||||
|
raise cls.errors[0]
|
||||||
|
finally:
|
||||||
|
NonGCSimpleBase._cleaning = True
|
||||||
|
cls._cleanup()
|
||||||
|
|
||||||
|
def check_sanity(self):
|
||||||
|
"""
|
||||||
|
Check the object is sane (non-broken).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""
|
||||||
|
PEP 442 finalizer. Record that this was called, check the
|
||||||
|
object is in a sane state, and invoke a side effect.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not self._cleaning:
|
||||||
|
self.del_calls.append(id(self))
|
||||||
|
self.check_sanity()
|
||||||
|
self.side_effect()
|
||||||
|
except Exception as e:
|
||||||
|
self.errors.append(e)
|
||||||
|
|
||||||
|
def side_effect(self):
|
||||||
|
"""
|
||||||
|
A side effect called on destruction.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleBase(NonGCSimpleBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.id_ = id(self)
|
||||||
|
|
||||||
|
def check_sanity(self):
|
||||||
|
assert self.id_ == id(self)
|
||||||
|
|
||||||
|
|
||||||
|
class NonGC(NonGCSimpleBase):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
class NonGCResurrector(NonGCSimpleBase):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def side_effect(self):
|
||||||
|
"""
|
||||||
|
Resurrect self by storing self in a class-wide list.
|
||||||
|
"""
|
||||||
|
self.survivors.append(self)
|
||||||
|
|
||||||
|
class Simple(SimpleBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SimpleResurrector(NonGCResurrector, SimpleBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestBase:
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.old_garbage = gc.garbage[:]
|
||||||
|
gc.garbage[:] = []
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# None of the tests here should put anything in gc.garbage
|
||||||
|
try:
|
||||||
|
self.assertEqual(gc.garbage, [])
|
||||||
|
finally:
|
||||||
|
del self.old_garbage
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
def assert_del_calls(self, ids):
|
||||||
|
self.assertEqual(sorted(SimpleBase.del_calls), sorted(ids))
|
||||||
|
|
||||||
|
def assert_tp_del_calls(self, ids):
|
||||||
|
self.assertEqual(sorted(SimpleBase.tp_del_calls), sorted(ids))
|
||||||
|
|
||||||
|
def assert_survivors(self, ids):
|
||||||
|
self.assertEqual(sorted(id(x) for x in SimpleBase.survivors), sorted(ids))
|
||||||
|
|
||||||
|
def assert_garbage(self, ids):
|
||||||
|
self.assertEqual(sorted(id(x) for x in gc.garbage), sorted(ids))
|
||||||
|
|
||||||
|
def clear_survivors(self):
|
||||||
|
SimpleBase.survivors.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleFinalizationTest(TestBase, unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test finalization without refcycles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = Simple()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
|
||||||
|
def test_simple_resurrect(self):
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = SimpleResurrector()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors(ids)
|
||||||
|
self.assertIsNot(wr(), None)
|
||||||
|
self.clear_survivors()
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
|
||||||
|
def test_non_gc(self):
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = NonGC()
|
||||||
|
self.assertFalse(gc.is_tracked(s))
|
||||||
|
ids = [id(s)]
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
|
||||||
|
def test_non_gc_resurrect(self):
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = NonGCResurrector()
|
||||||
|
self.assertFalse(gc.is_tracked(s))
|
||||||
|
ids = [id(s)]
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors(ids)
|
||||||
|
self.clear_survivors()
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids * 2)
|
||||||
|
self.assert_survivors(ids)
|
||||||
|
|
||||||
|
|
||||||
|
class SelfCycleBase:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.ref = self
|
||||||
|
|
||||||
|
def check_sanity(self):
|
||||||
|
super().check_sanity()
|
||||||
|
assert self.ref is self
|
||||||
|
|
||||||
|
class SimpleSelfCycle(SelfCycleBase, Simple):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SelfCycleResurrector(SelfCycleBase, SimpleResurrector):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SuicidalSelfCycle(SelfCycleBase, Simple):
|
||||||
|
|
||||||
|
def side_effect(self):
|
||||||
|
"""
|
||||||
|
Explicitly break the reference cycle.
|
||||||
|
"""
|
||||||
|
self.ref = None
|
||||||
|
|
||||||
|
|
||||||
|
class SelfCycleFinalizationTest(TestBase, unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test finalization of an object having a single cyclic reference to
|
||||||
|
itself.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = SimpleSelfCycle()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
|
||||||
|
def test_simple_resurrect(self):
|
||||||
|
# Test that __del__ can resurrect the object being finalized.
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = SelfCycleResurrector()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors(ids)
|
||||||
|
# XXX is this desirable?
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
# When trying to destroy the object a second time, __del__
|
||||||
|
# isn't called anymore (and the object isn't resurrected).
|
||||||
|
self.clear_survivors()
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
|
||||||
|
def test_simple_suicide(self):
|
||||||
|
# Test the GC is able to deal with an object that kills its last
|
||||||
|
# reference during __del__.
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = SuicidalSelfCycle()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
|
||||||
|
|
||||||
|
class ChainedBase:
|
||||||
|
|
||||||
|
def chain(self, left):
|
||||||
|
self.suicided = False
|
||||||
|
self.left = left
|
||||||
|
left.right = self
|
||||||
|
|
||||||
|
def check_sanity(self):
|
||||||
|
super().check_sanity()
|
||||||
|
if self.suicided:
|
||||||
|
assert self.left is None
|
||||||
|
assert self.right is None
|
||||||
|
else:
|
||||||
|
left = self.left
|
||||||
|
if left.suicided:
|
||||||
|
assert left.right is None
|
||||||
|
else:
|
||||||
|
assert left.right is self
|
||||||
|
right = self.right
|
||||||
|
if right.suicided:
|
||||||
|
assert right.left is None
|
||||||
|
else:
|
||||||
|
assert right.left is self
|
||||||
|
|
||||||
|
class SimpleChained(ChainedBase, Simple):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ChainedResurrector(ChainedBase, SimpleResurrector):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SuicidalChained(ChainedBase, Simple):
|
||||||
|
|
||||||
|
def side_effect(self):
|
||||||
|
"""
|
||||||
|
Explicitly break the reference cycle.
|
||||||
|
"""
|
||||||
|
self.suicided = True
|
||||||
|
self.left = None
|
||||||
|
self.right = None
|
||||||
|
|
||||||
|
|
||||||
|
class CycleChainFinalizationTest(TestBase, unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test finalization of a cyclic chain. These tests are similar in
|
||||||
|
spirit to the self-cycle tests above, but the collectable object
|
||||||
|
graph isn't trivial anymore.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def build_chain(self, classes):
|
||||||
|
nodes = [cls() for cls in classes]
|
||||||
|
for i in range(len(nodes)):
|
||||||
|
nodes[i].chain(nodes[i-1])
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
def check_non_resurrecting_chain(self, classes):
|
||||||
|
N = len(classes)
|
||||||
|
with SimpleBase.test():
|
||||||
|
nodes = self.build_chain(classes)
|
||||||
|
ids = [id(s) for s in nodes]
|
||||||
|
wrs = [weakref.ref(s) for s in nodes]
|
||||||
|
del nodes
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertEqual([wr() for wr in wrs], [None] * N)
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
|
||||||
|
def check_resurrecting_chain(self, classes):
|
||||||
|
N = len(classes)
|
||||||
|
with SimpleBase.test():
|
||||||
|
nodes = self.build_chain(classes)
|
||||||
|
N = len(nodes)
|
||||||
|
ids = [id(s) for s in nodes]
|
||||||
|
survivor_ids = [id(s) for s in nodes if isinstance(s, SimpleResurrector)]
|
||||||
|
wrs = [weakref.ref(s) for s in nodes]
|
||||||
|
del nodes
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors(survivor_ids)
|
||||||
|
# XXX desirable?
|
||||||
|
self.assertEqual([wr() for wr in wrs], [None] * N)
|
||||||
|
self.clear_survivors()
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
|
||||||
|
def test_homogenous(self):
|
||||||
|
self.check_non_resurrecting_chain([SimpleChained] * 3)
|
||||||
|
|
||||||
|
def test_homogenous_resurrect(self):
|
||||||
|
self.check_resurrecting_chain([ChainedResurrector] * 3)
|
||||||
|
|
||||||
|
def test_homogenous_suicidal(self):
|
||||||
|
self.check_non_resurrecting_chain([SuicidalChained] * 3)
|
||||||
|
|
||||||
|
def test_heterogenous_suicidal_one(self):
|
||||||
|
self.check_non_resurrecting_chain([SuicidalChained, SimpleChained] * 2)
|
||||||
|
|
||||||
|
def test_heterogenous_suicidal_two(self):
|
||||||
|
self.check_non_resurrecting_chain(
|
||||||
|
[SuicidalChained] * 2 + [SimpleChained] * 2)
|
||||||
|
|
||||||
|
def test_heterogenous_resurrect_one(self):
|
||||||
|
self.check_resurrecting_chain([ChainedResurrector, SimpleChained] * 2)
|
||||||
|
|
||||||
|
def test_heterogenous_resurrect_two(self):
|
||||||
|
self.check_resurrecting_chain(
|
||||||
|
[ChainedResurrector, SimpleChained, SuicidalChained] * 2)
|
||||||
|
|
||||||
|
def test_heterogenous_resurrect_three(self):
|
||||||
|
self.check_resurrecting_chain(
|
||||||
|
[ChainedResurrector] * 2 + [SimpleChained] * 2 + [SuicidalChained] * 2)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: the tp_del slot isn't automatically inherited, so we have to call
|
||||||
|
# with_tp_del() for each instantiated class.
|
||||||
|
|
||||||
|
class LegacyBase(SimpleBase):
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
# Do not invoke side_effect here, since we are now exercising
|
||||||
|
# the tp_del slot.
|
||||||
|
if not self._cleaning:
|
||||||
|
self.del_calls.append(id(self))
|
||||||
|
self.check_sanity()
|
||||||
|
except Exception as e:
|
||||||
|
self.errors.append(e)
|
||||||
|
|
||||||
|
def __tp_del__(self):
|
||||||
|
"""
|
||||||
|
Legacy (pre-PEP 442) finalizer, mapped to a tp_del slot.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not self._cleaning:
|
||||||
|
self.tp_del_calls.append(id(self))
|
||||||
|
self.check_sanity()
|
||||||
|
self.side_effect()
|
||||||
|
except Exception as e:
|
||||||
|
self.errors.append(e)
|
||||||
|
|
||||||
|
@_testcapi.with_tp_del
|
||||||
|
class Legacy(LegacyBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@_testcapi.with_tp_del
|
||||||
|
class LegacyResurrector(LegacyBase):
|
||||||
|
|
||||||
|
def side_effect(self):
|
||||||
|
"""
|
||||||
|
Resurrect self by storing self in a class-wide list.
|
||||||
|
"""
|
||||||
|
self.survivors.append(self)
|
||||||
|
|
||||||
|
@_testcapi.with_tp_del
|
||||||
|
class LegacySelfCycle(SelfCycleBase, LegacyBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyFinalizationTest(TestBase, unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Test finalization of objects with a tp_del.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# These tests need to clean up a bit more, since they create
|
||||||
|
# uncollectable objects.
|
||||||
|
gc.garbage.clear()
|
||||||
|
gc.collect()
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
|
def test_legacy(self):
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = Legacy()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_tp_del_calls(ids)
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_tp_del_calls(ids)
|
||||||
|
|
||||||
|
def test_legacy_resurrect(self):
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = LegacyResurrector()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_tp_del_calls(ids)
|
||||||
|
self.assert_survivors(ids)
|
||||||
|
# weakrefs are cleared before tp_del is called.
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
self.clear_survivors()
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls(ids)
|
||||||
|
self.assert_tp_del_calls(ids * 2)
|
||||||
|
self.assert_survivors(ids)
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
|
||||||
|
def test_legacy_self_cycle(self):
|
||||||
|
# Self-cycles with legacy finalizers end up in gc.garbage.
|
||||||
|
with SimpleBase.test():
|
||||||
|
s = LegacySelfCycle()
|
||||||
|
ids = [id(s)]
|
||||||
|
wr = weakref.ref(s)
|
||||||
|
del s
|
||||||
|
gc.collect()
|
||||||
|
self.assert_del_calls([])
|
||||||
|
self.assert_tp_del_calls([])
|
||||||
|
self.assert_survivors([])
|
||||||
|
self.assert_garbage(ids)
|
||||||
|
self.assertIsNot(wr(), None)
|
||||||
|
# Break the cycle to allow collection
|
||||||
|
gc.garbage[0].ref = None
|
||||||
|
self.assert_garbage([])
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_main():
|
||||||
|
support.run_unittest(__name__)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_main()
|
|
@ -1,3 +1,4 @@
|
||||||
|
import _testcapi
|
||||||
import unittest
|
import unittest
|
||||||
from test.support import (verbose, refcount_test, run_unittest,
|
from test.support import (verbose, refcount_test, run_unittest,
|
||||||
strip_python_stderr)
|
strip_python_stderr)
|
||||||
|
@ -40,6 +41,7 @@ class GC_Detector(object):
|
||||||
# gc collects it.
|
# gc collects it.
|
||||||
self.wr = weakref.ref(C1055820(666), it_happened)
|
self.wr = weakref.ref(C1055820(666), it_happened)
|
||||||
|
|
||||||
|
@_testcapi.with_tp_del
|
||||||
class Uncollectable(object):
|
class Uncollectable(object):
|
||||||
"""Create a reference cycle with multiple __del__ methods.
|
"""Create a reference cycle with multiple __del__ methods.
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ class Uncollectable(object):
|
||||||
self.partner = Uncollectable(partner=self)
|
self.partner = Uncollectable(partner=self)
|
||||||
else:
|
else:
|
||||||
self.partner = partner
|
self.partner = partner
|
||||||
def __del__(self):
|
def __tp_del__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
@ -141,11 +143,12 @@ class GCTests(unittest.TestCase):
|
||||||
del a
|
del a
|
||||||
self.assertNotEqual(gc.collect(), 0)
|
self.assertNotEqual(gc.collect(), 0)
|
||||||
|
|
||||||
def test_finalizer(self):
|
def test_legacy_finalizer(self):
|
||||||
# A() is uncollectable if it is part of a cycle, make sure it shows up
|
# A() is uncollectable if it is part of a cycle, make sure it shows up
|
||||||
# in gc.garbage.
|
# in gc.garbage.
|
||||||
|
@_testcapi.with_tp_del
|
||||||
class A:
|
class A:
|
||||||
def __del__(self): pass
|
def __tp_del__(self): pass
|
||||||
class B:
|
class B:
|
||||||
pass
|
pass
|
||||||
a = A()
|
a = A()
|
||||||
|
@ -165,11 +168,12 @@ class GCTests(unittest.TestCase):
|
||||||
self.fail("didn't find obj in garbage (finalizer)")
|
self.fail("didn't find obj in garbage (finalizer)")
|
||||||
gc.garbage.remove(obj)
|
gc.garbage.remove(obj)
|
||||||
|
|
||||||
def test_finalizer_newclass(self):
|
def test_legacy_finalizer_newclass(self):
|
||||||
# A() is uncollectable if it is part of a cycle, make sure it shows up
|
# A() is uncollectable if it is part of a cycle, make sure it shows up
|
||||||
# in gc.garbage.
|
# in gc.garbage.
|
||||||
|
@_testcapi.with_tp_del
|
||||||
class A(object):
|
class A(object):
|
||||||
def __del__(self): pass
|
def __tp_del__(self): pass
|
||||||
class B(object):
|
class B(object):
|
||||||
pass
|
pass
|
||||||
a = A()
|
a = A()
|
||||||
|
@ -570,12 +574,14 @@ class GCTests(unittest.TestCase):
|
||||||
import subprocess
|
import subprocess
|
||||||
code = """if 1:
|
code = """if 1:
|
||||||
import gc
|
import gc
|
||||||
|
import _testcapi
|
||||||
|
@_testcapi.with_tp_del
|
||||||
class X:
|
class X:
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<X %%r>" %% self.name
|
return "<X %%r>" %% self.name
|
||||||
def __del__(self):
|
def __tp_del__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
x = X('first')
|
x = X('first')
|
||||||
|
|
|
@ -1,3 +1,55 @@
|
||||||
|
import gc
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import weakref
|
||||||
|
|
||||||
|
from test import support
|
||||||
|
|
||||||
|
|
||||||
|
class FinalizationTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_frame_resurrect(self):
|
||||||
|
# A generator frame can be resurrected by a generator's finalization.
|
||||||
|
def gen():
|
||||||
|
nonlocal frame
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
frame = sys._getframe()
|
||||||
|
|
||||||
|
g = gen()
|
||||||
|
wr = weakref.ref(g)
|
||||||
|
next(g)
|
||||||
|
del g
|
||||||
|
support.gc_collect()
|
||||||
|
self.assertIs(wr(), None)
|
||||||
|
self.assertTrue(frame)
|
||||||
|
del frame
|
||||||
|
support.gc_collect()
|
||||||
|
|
||||||
|
def test_refcycle(self):
|
||||||
|
# A generator caught in a refcycle gets finalized anyway.
|
||||||
|
old_garbage = gc.garbage[:]
|
||||||
|
finalized = False
|
||||||
|
def gen():
|
||||||
|
nonlocal finalized
|
||||||
|
try:
|
||||||
|
g = yield
|
||||||
|
yield 1
|
||||||
|
finally:
|
||||||
|
finalized = True
|
||||||
|
|
||||||
|
g = gen()
|
||||||
|
next(g)
|
||||||
|
g.send(g)
|
||||||
|
self.assertGreater(sys.getrefcount(g), 2)
|
||||||
|
self.assertFalse(finalized)
|
||||||
|
del g
|
||||||
|
support.gc_collect()
|
||||||
|
self.assertTrue(finalized)
|
||||||
|
self.assertEqual(gc.garbage, old_garbage)
|
||||||
|
|
||||||
|
|
||||||
tutorial_tests = """
|
tutorial_tests = """
|
||||||
Let's try a simple generator:
|
Let's try a simple generator:
|
||||||
|
|
||||||
|
@ -1880,6 +1932,7 @@ __test__ = {"tut": tutorial_tests,
|
||||||
# so this works as expected in both ways of running regrtest.
|
# so this works as expected in both ways of running regrtest.
|
||||||
def test_main(verbose=None):
|
def test_main(verbose=None):
|
||||||
from test import support, test_generators
|
from test import support, test_generators
|
||||||
|
support.run_unittest(__name__)
|
||||||
support.run_doctest(test_generators, verbose)
|
support.run_doctest(test_generators, verbose)
|
||||||
|
|
||||||
# This part isn't needed for regrtest, but for running the test directly.
|
# This part isn't needed for regrtest, but for running the test directly.
|
||||||
|
|
|
@ -1072,12 +1072,13 @@ class CBufferedReaderTest(BufferedReaderTest, SizeofTest):
|
||||||
def test_garbage_collection(self):
|
def test_garbage_collection(self):
|
||||||
# C BufferedReader objects are collected.
|
# C BufferedReader objects are collected.
|
||||||
# The Python version has __del__, so it ends into gc.garbage instead
|
# The Python version has __del__, so it ends into gc.garbage instead
|
||||||
rawio = self.FileIO(support.TESTFN, "w+b")
|
with support.check_warnings(('', ResourceWarning)):
|
||||||
f = self.tp(rawio)
|
rawio = self.FileIO(support.TESTFN, "w+b")
|
||||||
f.f = f
|
f = self.tp(rawio)
|
||||||
wr = weakref.ref(f)
|
f.f = f
|
||||||
del f
|
wr = weakref.ref(f)
|
||||||
support.gc_collect()
|
del f
|
||||||
|
support.gc_collect()
|
||||||
self.assertTrue(wr() is None, wr)
|
self.assertTrue(wr() is None, wr)
|
||||||
|
|
||||||
def test_args_error(self):
|
def test_args_error(self):
|
||||||
|
@ -1366,13 +1367,14 @@ class CBufferedWriterTest(BufferedWriterTest, SizeofTest):
|
||||||
# C BufferedWriter objects are collected, and collecting them flushes
|
# C BufferedWriter objects are collected, and collecting them flushes
|
||||||
# all data to disk.
|
# all data to disk.
|
||||||
# The Python version has __del__, so it ends into gc.garbage instead
|
# The Python version has __del__, so it ends into gc.garbage instead
|
||||||
rawio = self.FileIO(support.TESTFN, "w+b")
|
with support.check_warnings(('', ResourceWarning)):
|
||||||
f = self.tp(rawio)
|
rawio = self.FileIO(support.TESTFN, "w+b")
|
||||||
f.write(b"123xxx")
|
f = self.tp(rawio)
|
||||||
f.x = f
|
f.write(b"123xxx")
|
||||||
wr = weakref.ref(f)
|
f.x = f
|
||||||
del f
|
wr = weakref.ref(f)
|
||||||
support.gc_collect()
|
del f
|
||||||
|
support.gc_collect()
|
||||||
self.assertTrue(wr() is None, wr)
|
self.assertTrue(wr() is None, wr)
|
||||||
with self.open(support.TESTFN, "rb") as f:
|
with self.open(support.TESTFN, "rb") as f:
|
||||||
self.assertEqual(f.read(), b"123xxx")
|
self.assertEqual(f.read(), b"123xxx")
|
||||||
|
@ -2607,14 +2609,15 @@ class CTextIOWrapperTest(TextIOWrapperTest):
|
||||||
# C TextIOWrapper objects are collected, and collecting them flushes
|
# C TextIOWrapper objects are collected, and collecting them flushes
|
||||||
# all data to disk.
|
# all data to disk.
|
||||||
# The Python version has __del__, so it ends in gc.garbage instead.
|
# The Python version has __del__, so it ends in gc.garbage instead.
|
||||||
rawio = io.FileIO(support.TESTFN, "wb")
|
with support.check_warnings(('', ResourceWarning)):
|
||||||
b = self.BufferedWriter(rawio)
|
rawio = io.FileIO(support.TESTFN, "wb")
|
||||||
t = self.TextIOWrapper(b, encoding="ascii")
|
b = self.BufferedWriter(rawio)
|
||||||
t.write("456def")
|
t = self.TextIOWrapper(b, encoding="ascii")
|
||||||
t.x = t
|
t.write("456def")
|
||||||
wr = weakref.ref(t)
|
t.x = t
|
||||||
del t
|
wr = weakref.ref(t)
|
||||||
support.gc_collect()
|
del t
|
||||||
|
support.gc_collect()
|
||||||
self.assertTrue(wr() is None, wr)
|
self.assertTrue(wr() is None, wr)
|
||||||
with self.open(support.TESTFN, "rb") as f:
|
with self.open(support.TESTFN, "rb") as f:
|
||||||
self.assertEqual(f.read(), b"456def")
|
self.assertEqual(f.read(), b"456def")
|
||||||
|
|
|
@ -864,11 +864,11 @@ class SizeofTest(unittest.TestCase):
|
||||||
check((1,2,3), vsize('') + 3*self.P)
|
check((1,2,3), vsize('') + 3*self.P)
|
||||||
# type
|
# type
|
||||||
# static type: PyTypeObject
|
# static type: PyTypeObject
|
||||||
s = vsize('P2n15Pl4Pn9Pn11PI')
|
s = vsize('P2n15Pl4Pn9Pn11PIP')
|
||||||
check(int, s)
|
check(int, s)
|
||||||
# (PyTypeObject + PyNumberMethods + PyMappingMethods +
|
# (PyTypeObject + PyNumberMethods + PyMappingMethods +
|
||||||
# PySequenceMethods + PyBufferProcs + 4P)
|
# PySequenceMethods + PyBufferProcs + 4P)
|
||||||
s = vsize('P2n15Pl4Pn9Pn11PI') + struct.calcsize('34P 3P 10P 2P 4P')
|
s = vsize('P2n15Pl4Pn9Pn11PIP') + struct.calcsize('34P 3P 10P 2P 4P')
|
||||||
# Separate block for PyDictKeysObject with 4 entries
|
# Separate block for PyDictKeysObject with 4 entries
|
||||||
s += struct.calcsize("2nPn") + 4*struct.calcsize("n2P")
|
s += struct.calcsize("2nPn") + 4*struct.calcsize("n2P")
|
||||||
# class
|
# class
|
||||||
|
|
|
@ -10,6 +10,8 @@ What's New in Python 3.4.0 Alpha 1?
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #18112: PEP 442 implementation (safe object finalization).
|
||||||
|
|
||||||
- Issue #18552: Check return value of PyArena_AddPyObject() in
|
- Issue #18552: Check return value of PyArena_AddPyObject() in
|
||||||
obj2ast_object().
|
obj2ast_object().
|
||||||
|
|
||||||
|
|
|
@ -190,7 +190,8 @@ PyTypeObject PyBufferedIOBase_Type = {
|
||||||
0, /*tp_getattro*/
|
0, /*tp_getattro*/
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
|
| Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
bufferediobase_doc, /* tp_doc */
|
bufferediobase_doc, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
0, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
|
@ -209,6 +210,16 @@ PyTypeObject PyBufferedIOBase_Type = {
|
||||||
0, /* tp_init */
|
0, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
0, /* tp_new */
|
0, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -220,7 +231,7 @@ typedef struct {
|
||||||
int detached;
|
int detached;
|
||||||
int readable;
|
int readable;
|
||||||
int writable;
|
int writable;
|
||||||
int deallocating;
|
char finalizing;
|
||||||
|
|
||||||
/* True if this is a vanilla Buffered object (rather than a user derived
|
/* True if this is a vanilla Buffered object (rather than a user derived
|
||||||
class) *and* the raw stream is a vanilla FileIO object. */
|
class) *and* the raw stream is a vanilla FileIO object. */
|
||||||
|
@ -384,8 +395,8 @@ _enter_buffered_busy(buffered *self)
|
||||||
static void
|
static void
|
||||||
buffered_dealloc(buffered *self)
|
buffered_dealloc(buffered *self)
|
||||||
{
|
{
|
||||||
self->deallocating = 1;
|
self->finalizing = 1;
|
||||||
if (self->ok && _PyIOBase_finalize((PyObject *) self) < 0)
|
if (_PyIOBase_finalize((PyObject *) self) < 0)
|
||||||
return;
|
return;
|
||||||
_PyObject_GC_UNTRACK(self);
|
_PyObject_GC_UNTRACK(self);
|
||||||
self->ok = 0;
|
self->ok = 0;
|
||||||
|
@ -428,8 +439,6 @@ buffered_traverse(buffered *self, visitproc visit, void *arg)
|
||||||
static int
|
static int
|
||||||
buffered_clear(buffered *self)
|
buffered_clear(buffered *self)
|
||||||
{
|
{
|
||||||
if (self->ok && _PyIOBase_finalize((PyObject *) self) < 0)
|
|
||||||
return -1;
|
|
||||||
self->ok = 0;
|
self->ok = 0;
|
||||||
Py_CLEAR(self->raw);
|
Py_CLEAR(self->raw);
|
||||||
Py_CLEAR(self->dict);
|
Py_CLEAR(self->dict);
|
||||||
|
@ -508,7 +517,7 @@ buffered_close(buffered *self, PyObject *args)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self->deallocating) {
|
if (self->finalizing) {
|
||||||
PyObject *r = buffered_dealloc_warn(self, (PyObject *) self);
|
PyObject *r = buffered_dealloc_warn(self, (PyObject *) self);
|
||||||
if (r)
|
if (r)
|
||||||
Py_DECREF(r);
|
Py_DECREF(r);
|
||||||
|
@ -1749,6 +1758,7 @@ static PyMethodDef bufferedreader_methods[] = {
|
||||||
|
|
||||||
static PyMemberDef bufferedreader_members[] = {
|
static PyMemberDef bufferedreader_members[] = {
|
||||||
{"raw", T_OBJECT, offsetof(buffered, raw), READONLY},
|
{"raw", T_OBJECT, offsetof(buffered, raw), READONLY},
|
||||||
|
{"_finalizing", T_BOOL, offsetof(buffered, finalizing), 0},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1781,7 +1791,7 @@ PyTypeObject PyBufferedReader_Type = {
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
| Py_TPFLAGS_HAVE_GC, /*tp_flags*/
|
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
bufferedreader_doc, /* tp_doc */
|
bufferedreader_doc, /* tp_doc */
|
||||||
(traverseproc)buffered_traverse, /* tp_traverse */
|
(traverseproc)buffered_traverse, /* tp_traverse */
|
||||||
(inquiry)buffered_clear, /* tp_clear */
|
(inquiry)buffered_clear, /* tp_clear */
|
||||||
|
@ -1800,6 +1810,16 @@ PyTypeObject PyBufferedReader_Type = {
|
||||||
(initproc)bufferedreader_init, /* tp_init */
|
(initproc)bufferedreader_init, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
PyType_GenericNew, /* tp_new */
|
PyType_GenericNew, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -2130,6 +2150,7 @@ static PyMethodDef bufferedwriter_methods[] = {
|
||||||
|
|
||||||
static PyMemberDef bufferedwriter_members[] = {
|
static PyMemberDef bufferedwriter_members[] = {
|
||||||
{"raw", T_OBJECT, offsetof(buffered, raw), READONLY},
|
{"raw", T_OBJECT, offsetof(buffered, raw), READONLY},
|
||||||
|
{"_finalizing", T_BOOL, offsetof(buffered, finalizing), 0},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2162,7 +2183,7 @@ PyTypeObject PyBufferedWriter_Type = {
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
| Py_TPFLAGS_HAVE_GC, /*tp_flags*/
|
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
bufferedwriter_doc, /* tp_doc */
|
bufferedwriter_doc, /* tp_doc */
|
||||||
(traverseproc)buffered_traverse, /* tp_traverse */
|
(traverseproc)buffered_traverse, /* tp_traverse */
|
||||||
(inquiry)buffered_clear, /* tp_clear */
|
(inquiry)buffered_clear, /* tp_clear */
|
||||||
|
@ -2181,6 +2202,16 @@ PyTypeObject PyBufferedWriter_Type = {
|
||||||
(initproc)bufferedwriter_init, /* tp_init */
|
(initproc)bufferedwriter_init, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
PyType_GenericNew, /* tp_new */
|
PyType_GenericNew, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -2416,7 +2447,7 @@ PyTypeObject PyBufferedRWPair_Type = {
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
| Py_TPFLAGS_HAVE_GC, /* tp_flags */
|
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /* tp_flags */
|
||||||
bufferedrwpair_doc, /* tp_doc */
|
bufferedrwpair_doc, /* tp_doc */
|
||||||
(traverseproc)bufferedrwpair_traverse, /* tp_traverse */
|
(traverseproc)bufferedrwpair_traverse, /* tp_traverse */
|
||||||
(inquiry)bufferedrwpair_clear, /* tp_clear */
|
(inquiry)bufferedrwpair_clear, /* tp_clear */
|
||||||
|
@ -2435,6 +2466,16 @@ PyTypeObject PyBufferedRWPair_Type = {
|
||||||
(initproc)bufferedrwpair_init, /* tp_init */
|
(initproc)bufferedrwpair_init, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
PyType_GenericNew, /* tp_new */
|
PyType_GenericNew, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -2522,6 +2563,7 @@ static PyMethodDef bufferedrandom_methods[] = {
|
||||||
|
|
||||||
static PyMemberDef bufferedrandom_members[] = {
|
static PyMemberDef bufferedrandom_members[] = {
|
||||||
{"raw", T_OBJECT, offsetof(buffered, raw), READONLY},
|
{"raw", T_OBJECT, offsetof(buffered, raw), READONLY},
|
||||||
|
{"_finalizing", T_BOOL, offsetof(buffered, finalizing), 0},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2554,7 +2596,7 @@ PyTypeObject PyBufferedRandom_Type = {
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
| Py_TPFLAGS_HAVE_GC, /*tp_flags*/
|
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
bufferedrandom_doc, /* tp_doc */
|
bufferedrandom_doc, /* tp_doc */
|
||||||
(traverseproc)buffered_traverse, /* tp_traverse */
|
(traverseproc)buffered_traverse, /* tp_traverse */
|
||||||
(inquiry)buffered_clear, /* tp_clear */
|
(inquiry)buffered_clear, /* tp_clear */
|
||||||
|
@ -2573,4 +2615,14 @@ PyTypeObject PyBufferedRandom_Type = {
|
||||||
(initproc)bufferedrandom_init, /* tp_init */
|
(initproc)bufferedrandom_init, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
PyType_GenericNew, /* tp_new */
|
PyType_GenericNew, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,7 +51,7 @@ typedef struct {
|
||||||
unsigned int writable : 1;
|
unsigned int writable : 1;
|
||||||
signed int seekable : 2; /* -1 means unknown */
|
signed int seekable : 2; /* -1 means unknown */
|
||||||
unsigned int closefd : 1;
|
unsigned int closefd : 1;
|
||||||
unsigned int deallocating: 1;
|
char finalizing;
|
||||||
PyObject *weakreflist;
|
PyObject *weakreflist;
|
||||||
PyObject *dict;
|
PyObject *dict;
|
||||||
} fileio;
|
} fileio;
|
||||||
|
@ -128,7 +128,7 @@ fileio_close(fileio *self)
|
||||||
self->fd = -1;
|
self->fd = -1;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
if (self->deallocating) {
|
if (self->finalizing) {
|
||||||
PyObject *r = fileio_dealloc_warn(self, (PyObject *) self);
|
PyObject *r = fileio_dealloc_warn(self, (PyObject *) self);
|
||||||
if (r)
|
if (r)
|
||||||
Py_DECREF(r);
|
Py_DECREF(r);
|
||||||
|
@ -447,7 +447,7 @@ fileio_clear(fileio *self)
|
||||||
static void
|
static void
|
||||||
fileio_dealloc(fileio *self)
|
fileio_dealloc(fileio *self)
|
||||||
{
|
{
|
||||||
self->deallocating = 1;
|
self->finalizing = 1;
|
||||||
if (_PyIOBase_finalize((PyObject *) self) < 0)
|
if (_PyIOBase_finalize((PyObject *) self) < 0)
|
||||||
return;
|
return;
|
||||||
_PyObject_GC_UNTRACK(self);
|
_PyObject_GC_UNTRACK(self);
|
||||||
|
@ -1182,6 +1182,11 @@ static PyGetSetDef fileio_getsetlist[] = {
|
||||||
{NULL},
|
{NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyMemberDef fileio_members[] = {
|
||||||
|
{"_finalizing", T_BOOL, offsetof(fileio, finalizing), 0},
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyTypeObject PyFileIO_Type = {
|
PyTypeObject PyFileIO_Type = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
"_io.FileIO",
|
"_io.FileIO",
|
||||||
|
@ -1203,7 +1208,7 @@ PyTypeObject PyFileIO_Type = {
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
| Py_TPFLAGS_HAVE_GC, /* tp_flags */
|
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /* tp_flags */
|
||||||
fileio_doc, /* tp_doc */
|
fileio_doc, /* tp_doc */
|
||||||
(traverseproc)fileio_traverse, /* tp_traverse */
|
(traverseproc)fileio_traverse, /* tp_traverse */
|
||||||
(inquiry)fileio_clear, /* tp_clear */
|
(inquiry)fileio_clear, /* tp_clear */
|
||||||
|
@ -1212,7 +1217,7 @@ PyTypeObject PyFileIO_Type = {
|
||||||
0, /* tp_iter */
|
0, /* tp_iter */
|
||||||
0, /* tp_iternext */
|
0, /* tp_iternext */
|
||||||
fileio_methods, /* tp_methods */
|
fileio_methods, /* tp_methods */
|
||||||
0, /* tp_members */
|
fileio_members, /* tp_members */
|
||||||
fileio_getsetlist, /* tp_getset */
|
fileio_getsetlist, /* tp_getset */
|
||||||
0, /* tp_base */
|
0, /* tp_base */
|
||||||
0, /* tp_dict */
|
0, /* tp_dict */
|
||||||
|
@ -1223,4 +1228,13 @@ PyTypeObject PyFileIO_Type = {
|
||||||
PyType_GenericAlloc, /* tp_alloc */
|
PyType_GenericAlloc, /* tp_alloc */
|
||||||
fileio_new, /* tp_new */
|
fileio_new, /* tp_new */
|
||||||
PyObject_GC_Del, /* tp_free */
|
PyObject_GC_Del, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
|
@ -196,21 +196,17 @@ iobase_close(PyObject *self, PyObject *args)
|
||||||
|
|
||||||
/* Finalization and garbage collection support */
|
/* Finalization and garbage collection support */
|
||||||
|
|
||||||
int
|
static void
|
||||||
_PyIOBase_finalize(PyObject *self)
|
iobase_finalize(PyObject *self)
|
||||||
{
|
{
|
||||||
PyObject *res;
|
PyObject *res;
|
||||||
PyObject *tp, *v, *tb;
|
PyObject *error_type, *error_value, *error_traceback;
|
||||||
int closed = 1;
|
int closed;
|
||||||
int is_zombie;
|
_Py_IDENTIFIER(_finalizing);
|
||||||
|
|
||||||
|
/* Save the current exception, if any. */
|
||||||
|
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||||
|
|
||||||
/* If _PyIOBase_finalize() is called from a destructor, we need to
|
|
||||||
resurrect the object as calling close() can invoke arbitrary code. */
|
|
||||||
is_zombie = (Py_REFCNT(self) == 0);
|
|
||||||
if (is_zombie) {
|
|
||||||
++Py_REFCNT(self);
|
|
||||||
}
|
|
||||||
PyErr_Fetch(&tp, &v, &tb);
|
|
||||||
/* If `closed` doesn't exist or can't be evaluated as bool, then the
|
/* If `closed` doesn't exist or can't be evaluated as bool, then the
|
||||||
object is probably in an unusable state, so ignore. */
|
object is probably in an unusable state, so ignore. */
|
||||||
res = PyObject_GetAttr(self, _PyIO_str_closed);
|
res = PyObject_GetAttr(self, _PyIO_str_closed);
|
||||||
|
@ -223,6 +219,10 @@ _PyIOBase_finalize(PyObject *self)
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
if (closed == 0) {
|
if (closed == 0) {
|
||||||
|
/* Signal close() that it was called as part of the object
|
||||||
|
finalization process. */
|
||||||
|
if (_PyObject_SetAttrId(self, &PyId__finalizing, Py_True))
|
||||||
|
PyErr_Clear();
|
||||||
res = PyObject_CallMethodObjArgs((PyObject *) self, _PyIO_str_close,
|
res = PyObject_CallMethodObjArgs((PyObject *) self, _PyIO_str_close,
|
||||||
NULL);
|
NULL);
|
||||||
/* Silencing I/O errors is bad, but printing spurious tracebacks is
|
/* Silencing I/O errors is bad, but printing spurious tracebacks is
|
||||||
|
@ -233,31 +233,25 @@ _PyIOBase_finalize(PyObject *self)
|
||||||
else
|
else
|
||||||
Py_DECREF(res);
|
Py_DECREF(res);
|
||||||
}
|
}
|
||||||
PyErr_Restore(tp, v, tb);
|
|
||||||
if (is_zombie) {
|
/* Restore the saved exception. */
|
||||||
if (--Py_REFCNT(self) != 0) {
|
PyErr_Restore(error_type, error_value, error_traceback);
|
||||||
/* The object lives again. The following code is taken from
|
}
|
||||||
slot_tp_del in typeobject.c. */
|
|
||||||
Py_ssize_t refcnt = Py_REFCNT(self);
|
int
|
||||||
_Py_NewReference(self);
|
_PyIOBase_finalize(PyObject *self)
|
||||||
Py_REFCNT(self) = refcnt;
|
{
|
||||||
/* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
|
int is_zombie;
|
||||||
* we need to undo that. */
|
|
||||||
_Py_DEC_REFTOTAL;
|
/* If _PyIOBase_finalize() is called from a destructor, we need to
|
||||||
/* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
|
resurrect the object as calling close() can invoke arbitrary code. */
|
||||||
* chain, so no more to do there.
|
is_zombie = (Py_REFCNT(self) == 0);
|
||||||
* If COUNT_ALLOCS, the original decref bumped tp_frees, and
|
if (is_zombie)
|
||||||
* _Py_NewReference bumped tp_allocs: both of those need to be
|
return PyObject_CallFinalizerFromDealloc(self);
|
||||||
* undone.
|
else {
|
||||||
*/
|
PyObject_CallFinalizer(self);
|
||||||
#ifdef COUNT_ALLOCS
|
return 0;
|
||||||
--Py_TYPE(self)->tp_frees;
|
|
||||||
--Py_TYPE(self)->tp_allocs;
|
|
||||||
#endif
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -270,8 +264,6 @@ iobase_traverse(iobase *self, visitproc visit, void *arg)
|
||||||
static int
|
static int
|
||||||
iobase_clear(iobase *self)
|
iobase_clear(iobase *self)
|
||||||
{
|
{
|
||||||
if (_PyIOBase_finalize((PyObject *) self) < 0)
|
|
||||||
return -1;
|
|
||||||
Py_CLEAR(self->dict);
|
Py_CLEAR(self->dict);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -741,7 +733,7 @@ PyTypeObject PyIOBase_Type = {
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
| Py_TPFLAGS_HAVE_GC, /*tp_flags*/
|
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
iobase_doc, /* tp_doc */
|
iobase_doc, /* tp_doc */
|
||||||
(traverseproc)iobase_traverse, /* tp_traverse */
|
(traverseproc)iobase_traverse, /* tp_traverse */
|
||||||
(inquiry)iobase_clear, /* tp_clear */
|
(inquiry)iobase_clear, /* tp_clear */
|
||||||
|
@ -760,6 +752,16 @@ PyTypeObject PyIOBase_Type = {
|
||||||
0, /* tp_init */
|
0, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
PyType_GenericNew, /* tp_new */
|
PyType_GenericNew, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
(destructor)iobase_finalize, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -905,7 +907,7 @@ PyTypeObject PyRawIOBase_Type = {
|
||||||
0, /*tp_getattro*/
|
0, /*tp_getattro*/
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
rawiobase_doc, /* tp_doc */
|
rawiobase_doc, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
0, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
|
@ -924,4 +926,14 @@ PyTypeObject PyRawIOBase_Type = {
|
||||||
0, /* tp_init */
|
0, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
0, /* tp_new */
|
0, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
|
@ -173,7 +173,8 @@ PyTypeObject PyTextIOBase_Type = {
|
||||||
0, /*tp_getattro*/
|
0, /*tp_getattro*/
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
|
| Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
textiobase_doc, /* tp_doc */
|
textiobase_doc, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
0, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
|
@ -192,6 +193,16 @@ PyTypeObject PyTextIOBase_Type = {
|
||||||
0, /* tp_init */
|
0, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
0, /* tp_new */
|
0, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -691,7 +702,7 @@ typedef struct
|
||||||
char seekable;
|
char seekable;
|
||||||
char has_read1;
|
char has_read1;
|
||||||
char telling;
|
char telling;
|
||||||
char deallocating;
|
char finalizing;
|
||||||
/* Specialized encoding func (see below) */
|
/* Specialized encoding func (see below) */
|
||||||
encodefunc_t encodefunc;
|
encodefunc_t encodefunc;
|
||||||
/* Whether or not it's the start of the stream */
|
/* Whether or not it's the start of the stream */
|
||||||
|
@ -1112,8 +1123,6 @@ textiowrapper_init(textio *self, PyObject *args, PyObject *kwds)
|
||||||
static int
|
static int
|
||||||
_textiowrapper_clear(textio *self)
|
_textiowrapper_clear(textio *self)
|
||||||
{
|
{
|
||||||
if (self->ok && _PyIOBase_finalize((PyObject *) self) < 0)
|
|
||||||
return -1;
|
|
||||||
self->ok = 0;
|
self->ok = 0;
|
||||||
Py_CLEAR(self->buffer);
|
Py_CLEAR(self->buffer);
|
||||||
Py_CLEAR(self->encoding);
|
Py_CLEAR(self->encoding);
|
||||||
|
@ -1131,9 +1140,10 @@ _textiowrapper_clear(textio *self)
|
||||||
static void
|
static void
|
||||||
textiowrapper_dealloc(textio *self)
|
textiowrapper_dealloc(textio *self)
|
||||||
{
|
{
|
||||||
self->deallocating = 1;
|
self->finalizing = 1;
|
||||||
if (_textiowrapper_clear(self) < 0)
|
if (_PyIOBase_finalize((PyObject *) self) < 0)
|
||||||
return;
|
return;
|
||||||
|
_textiowrapper_clear(self);
|
||||||
_PyObject_GC_UNTRACK(self);
|
_PyObject_GC_UNTRACK(self);
|
||||||
if (self->weakreflist != NULL)
|
if (self->weakreflist != NULL)
|
||||||
PyObject_ClearWeakRefs((PyObject *)self);
|
PyObject_ClearWeakRefs((PyObject *)self);
|
||||||
|
@ -2573,7 +2583,7 @@ textiowrapper_close(textio *self, PyObject *args)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PyObject *exc = NULL, *val, *tb;
|
PyObject *exc = NULL, *val, *tb;
|
||||||
if (self->deallocating) {
|
if (self->finalizing) {
|
||||||
res = _PyObject_CallMethodId(self->buffer, &PyId__dealloc_warn, "O", self);
|
res = _PyObject_CallMethodId(self->buffer, &PyId__dealloc_warn, "O", self);
|
||||||
if (res)
|
if (res)
|
||||||
Py_DECREF(res);
|
Py_DECREF(res);
|
||||||
|
@ -2734,6 +2744,7 @@ static PyMemberDef textiowrapper_members[] = {
|
||||||
{"encoding", T_OBJECT, offsetof(textio, encoding), READONLY},
|
{"encoding", T_OBJECT, offsetof(textio, encoding), READONLY},
|
||||||
{"buffer", T_OBJECT, offsetof(textio, buffer), READONLY},
|
{"buffer", T_OBJECT, offsetof(textio, buffer), READONLY},
|
||||||
{"line_buffering", T_BOOL, offsetof(textio, line_buffering), READONLY},
|
{"line_buffering", T_BOOL, offsetof(textio, line_buffering), READONLY},
|
||||||
|
{"_finalizing", T_BOOL, offsetof(textio, finalizing), 0},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2770,7 +2781,7 @@ PyTypeObject PyTextIOWrapper_Type = {
|
||||||
0, /*tp_setattro*/
|
0, /*tp_setattro*/
|
||||||
0, /*tp_as_buffer*/
|
0, /*tp_as_buffer*/
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
|
||||||
| Py_TPFLAGS_HAVE_GC, /*tp_flags*/
|
| Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /*tp_flags*/
|
||||||
textiowrapper_doc, /* tp_doc */
|
textiowrapper_doc, /* tp_doc */
|
||||||
(traverseproc)textiowrapper_traverse, /* tp_traverse */
|
(traverseproc)textiowrapper_traverse, /* tp_traverse */
|
||||||
(inquiry)textiowrapper_clear, /* tp_clear */
|
(inquiry)textiowrapper_clear, /* tp_clear */
|
||||||
|
@ -2789,4 +2800,14 @@ PyTypeObject PyTextIOWrapper_Type = {
|
||||||
(initproc)textiowrapper_init, /* tp_init */
|
(initproc)textiowrapper_init, /* tp_init */
|
||||||
0, /* tp_alloc */
|
0, /* tp_alloc */
|
||||||
PyType_GenericNew, /* tp_new */
|
PyType_GenericNew, /* tp_new */
|
||||||
|
0, /* tp_free */
|
||||||
|
0, /* tp_is_gc */
|
||||||
|
0, /* tp_bases */
|
||||||
|
0, /* tp_mro */
|
||||||
|
0, /* tp_cache */
|
||||||
|
0, /* tp_subclasses */
|
||||||
|
0, /* tp_weaklist */
|
||||||
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
0, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
|
@ -2491,6 +2491,85 @@ test_pytime_object_to_timespec(PyObject *self, PyObject *args)
|
||||||
return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec);
|
return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
slot_tp_del(PyObject *self)
|
||||||
|
{
|
||||||
|
_Py_IDENTIFIER(__tp_del__);
|
||||||
|
PyObject *del, *res;
|
||||||
|
PyObject *error_type, *error_value, *error_traceback;
|
||||||
|
|
||||||
|
/* Temporarily resurrect the object. */
|
||||||
|
assert(self->ob_refcnt == 0);
|
||||||
|
self->ob_refcnt = 1;
|
||||||
|
|
||||||
|
/* Save the current exception, if any. */
|
||||||
|
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||||
|
|
||||||
|
/* Execute __del__ method, if any. */
|
||||||
|
del = _PyObject_LookupSpecial(self, &PyId___tp_del__);
|
||||||
|
if (del != NULL) {
|
||||||
|
res = PyEval_CallObject(del, NULL);
|
||||||
|
if (res == NULL)
|
||||||
|
PyErr_WriteUnraisable(del);
|
||||||
|
else
|
||||||
|
Py_DECREF(res);
|
||||||
|
Py_DECREF(del);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Restore the saved exception. */
|
||||||
|
PyErr_Restore(error_type, error_value, error_traceback);
|
||||||
|
|
||||||
|
/* Undo the temporary resurrection; can't use DECREF here, it would
|
||||||
|
* cause a recursive call.
|
||||||
|
*/
|
||||||
|
assert(self->ob_refcnt > 0);
|
||||||
|
if (--self->ob_refcnt == 0)
|
||||||
|
return; /* this is the normal path out */
|
||||||
|
|
||||||
|
/* __del__ resurrected it! Make it look like the original Py_DECREF
|
||||||
|
* never happened.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
Py_ssize_t refcnt = self->ob_refcnt;
|
||||||
|
_Py_NewReference(self);
|
||||||
|
self->ob_refcnt = refcnt;
|
||||||
|
}
|
||||||
|
assert(!PyType_IS_GC(Py_TYPE(self)) ||
|
||||||
|
_Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
|
||||||
|
/* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
|
||||||
|
* we need to undo that. */
|
||||||
|
_Py_DEC_REFTOTAL;
|
||||||
|
/* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
|
||||||
|
* chain, so no more to do there.
|
||||||
|
* If COUNT_ALLOCS, the original decref bumped tp_frees, and
|
||||||
|
* _Py_NewReference bumped tp_allocs: both of those need to be
|
||||||
|
* undone.
|
||||||
|
*/
|
||||||
|
#ifdef COUNT_ALLOCS
|
||||||
|
--Py_TYPE(self)->tp_frees;
|
||||||
|
--Py_TYPE(self)->tp_allocs;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
with_tp_del(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
PyObject *obj;
|
||||||
|
PyTypeObject *tp;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "O:with_tp_del", &obj))
|
||||||
|
return NULL;
|
||||||
|
tp = (PyTypeObject *) obj;
|
||||||
|
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"heap type expected, got %R", obj);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
tp->tp_del = slot_tp_del;
|
||||||
|
Py_INCREF(obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_test_incref(PyObject *ob)
|
_test_incref(PyObject *ob)
|
||||||
{
|
{
|
||||||
|
@ -2789,6 +2868,7 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"pytime_object_to_time_t", test_pytime_object_to_time_t, METH_VARARGS},
|
{"pytime_object_to_time_t", test_pytime_object_to_time_t, METH_VARARGS},
|
||||||
{"pytime_object_to_timeval", test_pytime_object_to_timeval, METH_VARARGS},
|
{"pytime_object_to_timeval", test_pytime_object_to_timeval, METH_VARARGS},
|
||||||
{"pytime_object_to_timespec", test_pytime_object_to_timespec, METH_VARARGS},
|
{"pytime_object_to_timespec", test_pytime_object_to_timespec, METH_VARARGS},
|
||||||
|
{"with_tp_del", with_tp_del, METH_VARARGS},
|
||||||
{"test_pymem",
|
{"test_pymem",
|
||||||
(PyCFunction)test_pymem_alloc0, METH_NOARGS},
|
(PyCFunction)test_pymem_alloc0, METH_NOARGS},
|
||||||
{"test_pymem_alloc0",
|
{"test_pymem_alloc0",
|
||||||
|
|
|
@ -223,10 +223,10 @@ GC_TENTATIVELY_UNREACHABLE
|
||||||
#define GC_REACHABLE _PyGC_REFS_REACHABLE
|
#define GC_REACHABLE _PyGC_REFS_REACHABLE
|
||||||
#define GC_TENTATIVELY_UNREACHABLE _PyGC_REFS_TENTATIVELY_UNREACHABLE
|
#define GC_TENTATIVELY_UNREACHABLE _PyGC_REFS_TENTATIVELY_UNREACHABLE
|
||||||
|
|
||||||
#define IS_TRACKED(o) ((AS_GC(o))->gc.gc_refs != GC_UNTRACKED)
|
#define IS_TRACKED(o) (_PyGC_REFS(o) != GC_UNTRACKED)
|
||||||
#define IS_REACHABLE(o) ((AS_GC(o))->gc.gc_refs == GC_REACHABLE)
|
#define IS_REACHABLE(o) (_PyGC_REFS(o) == GC_REACHABLE)
|
||||||
#define IS_TENTATIVELY_UNREACHABLE(o) ( \
|
#define IS_TENTATIVELY_UNREACHABLE(o) ( \
|
||||||
(AS_GC(o))->gc.gc_refs == GC_TENTATIVELY_UNREACHABLE)
|
_PyGC_REFS(o) == GC_TENTATIVELY_UNREACHABLE)
|
||||||
|
|
||||||
/*** list functions ***/
|
/*** list functions ***/
|
||||||
|
|
||||||
|
@ -341,8 +341,8 @@ update_refs(PyGC_Head *containers)
|
||||||
{
|
{
|
||||||
PyGC_Head *gc = containers->gc.gc_next;
|
PyGC_Head *gc = containers->gc.gc_next;
|
||||||
for (; gc != containers; gc = gc->gc.gc_next) {
|
for (; gc != containers; gc = gc->gc.gc_next) {
|
||||||
assert(gc->gc.gc_refs == GC_REACHABLE);
|
assert(_PyGCHead_REFS(gc) == GC_REACHABLE);
|
||||||
gc->gc.gc_refs = Py_REFCNT(FROM_GC(gc));
|
_PyGCHead_SET_REFS(gc, Py_REFCNT(FROM_GC(gc)));
|
||||||
/* Python's cyclic gc should never see an incoming refcount
|
/* Python's cyclic gc should never see an incoming refcount
|
||||||
* of 0: if something decref'ed to 0, it should have been
|
* of 0: if something decref'ed to 0, it should have been
|
||||||
* deallocated immediately at that time.
|
* deallocated immediately at that time.
|
||||||
|
@ -361,7 +361,7 @@ update_refs(PyGC_Head *containers)
|
||||||
* so serious that maybe this should be a release-build
|
* so serious that maybe this should be a release-build
|
||||||
* check instead of an assert?
|
* check instead of an assert?
|
||||||
*/
|
*/
|
||||||
assert(gc->gc.gc_refs != 0);
|
assert(_PyGCHead_REFS(gc) != 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,9 +376,9 @@ visit_decref(PyObject *op, void *data)
|
||||||
* generation being collected, which can be recognized
|
* generation being collected, which can be recognized
|
||||||
* because only they have positive gc_refs.
|
* because only they have positive gc_refs.
|
||||||
*/
|
*/
|
||||||
assert(gc->gc.gc_refs != 0); /* else refcount was too small */
|
assert(_PyGCHead_REFS(gc) != 0); /* else refcount was too small */
|
||||||
if (gc->gc.gc_refs > 0)
|
if (_PyGCHead_REFS(gc) > 0)
|
||||||
gc->gc.gc_refs--;
|
_PyGCHead_DECREF(gc);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -407,7 +407,7 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
|
||||||
{
|
{
|
||||||
if (PyObject_IS_GC(op)) {
|
if (PyObject_IS_GC(op)) {
|
||||||
PyGC_Head *gc = AS_GC(op);
|
PyGC_Head *gc = AS_GC(op);
|
||||||
const Py_ssize_t gc_refs = gc->gc.gc_refs;
|
const Py_ssize_t gc_refs = _PyGCHead_REFS(gc);
|
||||||
|
|
||||||
if (gc_refs == 0) {
|
if (gc_refs == 0) {
|
||||||
/* This is in move_unreachable's 'young' list, but
|
/* This is in move_unreachable's 'young' list, but
|
||||||
|
@ -415,7 +415,7 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
|
||||||
* we need to do is tell move_unreachable that it's
|
* we need to do is tell move_unreachable that it's
|
||||||
* reachable.
|
* reachable.
|
||||||
*/
|
*/
|
||||||
gc->gc.gc_refs = 1;
|
_PyGCHead_SET_REFS(gc, 1);
|
||||||
}
|
}
|
||||||
else if (gc_refs == GC_TENTATIVELY_UNREACHABLE) {
|
else if (gc_refs == GC_TENTATIVELY_UNREACHABLE) {
|
||||||
/* This had gc_refs = 0 when move_unreachable got
|
/* This had gc_refs = 0 when move_unreachable got
|
||||||
|
@ -425,7 +425,7 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
|
||||||
* again.
|
* again.
|
||||||
*/
|
*/
|
||||||
gc_list_move(gc, reachable);
|
gc_list_move(gc, reachable);
|
||||||
gc->gc.gc_refs = 1;
|
_PyGCHead_SET_REFS(gc, 1);
|
||||||
}
|
}
|
||||||
/* Else there's nothing to do.
|
/* Else there's nothing to do.
|
||||||
* If gc_refs > 0, it must be in move_unreachable's 'young'
|
* If gc_refs > 0, it must be in move_unreachable's 'young'
|
||||||
|
@ -469,7 +469,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
|
||||||
while (gc != young) {
|
while (gc != young) {
|
||||||
PyGC_Head *next;
|
PyGC_Head *next;
|
||||||
|
|
||||||
if (gc->gc.gc_refs) {
|
if (_PyGCHead_REFS(gc)) {
|
||||||
/* gc is definitely reachable from outside the
|
/* gc is definitely reachable from outside the
|
||||||
* original 'young'. Mark it as such, and traverse
|
* original 'young'. Mark it as such, and traverse
|
||||||
* its pointers to find any other objects that may
|
* its pointers to find any other objects that may
|
||||||
|
@ -480,8 +480,8 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
|
||||||
*/
|
*/
|
||||||
PyObject *op = FROM_GC(gc);
|
PyObject *op = FROM_GC(gc);
|
||||||
traverseproc traverse = Py_TYPE(op)->tp_traverse;
|
traverseproc traverse = Py_TYPE(op)->tp_traverse;
|
||||||
assert(gc->gc.gc_refs > 0);
|
assert(_PyGCHead_REFS(gc) > 0);
|
||||||
gc->gc.gc_refs = GC_REACHABLE;
|
_PyGCHead_SET_REFS(gc, GC_REACHABLE);
|
||||||
(void) traverse(op,
|
(void) traverse(op,
|
||||||
(visitproc)visit_reachable,
|
(visitproc)visit_reachable,
|
||||||
(void *)young);
|
(void *)young);
|
||||||
|
@ -500,7 +500,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
|
||||||
*/
|
*/
|
||||||
next = gc->gc.gc_next;
|
next = gc->gc.gc_next;
|
||||||
gc_list_move(gc, unreachable);
|
gc_list_move(gc, unreachable);
|
||||||
gc->gc.gc_refs = GC_TENTATIVELY_UNREACHABLE;
|
_PyGCHead_SET_REFS(gc, GC_TENTATIVELY_UNREACHABLE);
|
||||||
}
|
}
|
||||||
gc = next;
|
gc = next;
|
||||||
}
|
}
|
||||||
|
@ -520,22 +520,19 @@ untrack_dicts(PyGC_Head *head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return true if object has a finalization method. */
|
/* Return true if object has a pre-PEP 442 finalization method. */
|
||||||
static int
|
static int
|
||||||
has_finalizer(PyObject *op)
|
has_legacy_finalizer(PyObject *op)
|
||||||
{
|
{
|
||||||
if (PyGen_CheckExact(op))
|
return op->ob_type->tp_del != NULL;
|
||||||
return PyGen_NeedsFinalizing((PyGenObject *)op);
|
|
||||||
else
|
|
||||||
return op->ob_type->tp_del != NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move the objects in unreachable with __del__ methods into `finalizers`.
|
/* Move the objects in unreachable with tp_del slots into `finalizers`.
|
||||||
* Objects moved into `finalizers` have gc_refs set to GC_REACHABLE; the
|
* Objects moved into `finalizers` have gc_refs set to GC_REACHABLE; the
|
||||||
* objects remaining in unreachable are left at GC_TENTATIVELY_UNREACHABLE.
|
* objects remaining in unreachable are left at GC_TENTATIVELY_UNREACHABLE.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
move_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
|
move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
|
||||||
{
|
{
|
||||||
PyGC_Head *gc;
|
PyGC_Head *gc;
|
||||||
PyGC_Head *next;
|
PyGC_Head *next;
|
||||||
|
@ -549,14 +546,14 @@ move_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
|
||||||
assert(IS_TENTATIVELY_UNREACHABLE(op));
|
assert(IS_TENTATIVELY_UNREACHABLE(op));
|
||||||
next = gc->gc.gc_next;
|
next = gc->gc.gc_next;
|
||||||
|
|
||||||
if (has_finalizer(op)) {
|
if (has_legacy_finalizer(op)) {
|
||||||
gc_list_move(gc, finalizers);
|
gc_list_move(gc, finalizers);
|
||||||
gc->gc.gc_refs = GC_REACHABLE;
|
_PyGCHead_SET_REFS(gc, GC_REACHABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A traversal callback for move_finalizer_reachable. */
|
/* A traversal callback for move_legacy_finalizer_reachable. */
|
||||||
static int
|
static int
|
||||||
visit_move(PyObject *op, PyGC_Head *tolist)
|
visit_move(PyObject *op, PyGC_Head *tolist)
|
||||||
{
|
{
|
||||||
|
@ -564,7 +561,7 @@ visit_move(PyObject *op, PyGC_Head *tolist)
|
||||||
if (IS_TENTATIVELY_UNREACHABLE(op)) {
|
if (IS_TENTATIVELY_UNREACHABLE(op)) {
|
||||||
PyGC_Head *gc = AS_GC(op);
|
PyGC_Head *gc = AS_GC(op);
|
||||||
gc_list_move(gc, tolist);
|
gc_list_move(gc, tolist);
|
||||||
gc->gc.gc_refs = GC_REACHABLE;
|
_PyGCHead_SET_REFS(gc, GC_REACHABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -574,7 +571,7 @@ visit_move(PyObject *op, PyGC_Head *tolist)
|
||||||
* into finalizers set.
|
* into finalizers set.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
move_finalizer_reachable(PyGC_Head *finalizers)
|
move_legacy_finalizer_reachable(PyGC_Head *finalizers)
|
||||||
{
|
{
|
||||||
traverseproc traverse;
|
traverseproc traverse;
|
||||||
PyGC_Head *gc = finalizers->gc.gc_next;
|
PyGC_Head *gc = finalizers->gc.gc_next;
|
||||||
|
@ -747,7 +744,7 @@ debug_cycle(char *msg, PyObject *op)
|
||||||
msg, Py_TYPE(op)->tp_name, op);
|
msg, Py_TYPE(op)->tp_name, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle uncollectable garbage (cycles with finalizers, and stuff reachable
|
/* Handle uncollectable garbage (cycles with tp_del slots, and stuff reachable
|
||||||
* only from such cycles).
|
* only from such cycles).
|
||||||
* If DEBUG_SAVEALL, all objects in finalizers are appended to the module
|
* If DEBUG_SAVEALL, all objects in finalizers are appended to the module
|
||||||
* garbage list (a Python list), else only the objects in finalizers with
|
* garbage list (a Python list), else only the objects in finalizers with
|
||||||
|
@ -757,7 +754,7 @@ debug_cycle(char *msg, PyObject *op)
|
||||||
* The finalizers list is made empty on a successful return.
|
* The finalizers list is made empty on a successful return.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old)
|
handle_legacy_finalizers(PyGC_Head *finalizers, PyGC_Head *old)
|
||||||
{
|
{
|
||||||
PyGC_Head *gc = finalizers->gc.gc_next;
|
PyGC_Head *gc = finalizers->gc.gc_next;
|
||||||
|
|
||||||
|
@ -769,7 +766,7 @@ handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old)
|
||||||
for (; gc != finalizers; gc = gc->gc.gc_next) {
|
for (; gc != finalizers; gc = gc->gc.gc_next) {
|
||||||
PyObject *op = FROM_GC(gc);
|
PyObject *op = FROM_GC(gc);
|
||||||
|
|
||||||
if ((debug & DEBUG_SAVEALL) || has_finalizer(op)) {
|
if ((debug & DEBUG_SAVEALL) || has_legacy_finalizer(op)) {
|
||||||
if (PyList_Append(garbage, op) < 0)
|
if (PyList_Append(garbage, op) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -779,6 +776,62 @@ handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
finalize_garbage(PyGC_Head *collectable, PyGC_Head *old)
|
||||||
|
{
|
||||||
|
destructor finalize;
|
||||||
|
PyGC_Head *gc = collectable->gc.gc_next;
|
||||||
|
|
||||||
|
for (; gc != collectable; gc = gc->gc.gc_next) {
|
||||||
|
PyObject *op = FROM_GC(gc);
|
||||||
|
|
||||||
|
if (!_PyGCHead_FINALIZED(gc) &&
|
||||||
|
PyType_HasFeature(Py_TYPE(op), Py_TPFLAGS_HAVE_FINALIZE) &&
|
||||||
|
(finalize = Py_TYPE(op)->tp_finalize) != NULL) {
|
||||||
|
_PyGCHead_SET_FINALIZED(gc, 1);
|
||||||
|
Py_INCREF(op);
|
||||||
|
finalize(op);
|
||||||
|
if (Py_REFCNT(op) == 1) {
|
||||||
|
/* op will be destroyed */
|
||||||
|
gc = gc->gc.gc_prev;
|
||||||
|
}
|
||||||
|
Py_DECREF(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Walk the collectable list and check that they are really unreachable
|
||||||
|
from the outside (some objects could have been resurrected by a
|
||||||
|
finalizer). */
|
||||||
|
static int
|
||||||
|
check_garbage(PyGC_Head *collectable)
|
||||||
|
{
|
||||||
|
PyGC_Head *gc;
|
||||||
|
for (gc = collectable->gc.gc_next; gc != collectable;
|
||||||
|
gc = gc->gc.gc_next) {
|
||||||
|
_PyGCHead_SET_REFS(gc, Py_REFCNT(FROM_GC(gc)));
|
||||||
|
assert(_PyGCHead_REFS(gc) != 0);
|
||||||
|
}
|
||||||
|
subtract_refs(collectable);
|
||||||
|
for (gc = collectable->gc.gc_next; gc != collectable;
|
||||||
|
gc = gc->gc.gc_next) {
|
||||||
|
assert(_PyGCHead_REFS(gc) >= 0);
|
||||||
|
if (_PyGCHead_REFS(gc) != 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
revive_garbage(PyGC_Head *collectable)
|
||||||
|
{
|
||||||
|
PyGC_Head *gc;
|
||||||
|
for (gc = collectable->gc.gc_next; gc != collectable;
|
||||||
|
gc = gc->gc.gc_next) {
|
||||||
|
_PyGCHead_SET_REFS(gc, GC_REACHABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Break reference cycles by clearing the containers involved. This is
|
/* Break reference cycles by clearing the containers involved. This is
|
||||||
* tricky business as the lists can be changing and we don't know which
|
* tricky business as the lists can be changing and we don't know which
|
||||||
* objects may be freed. It is possible I screwed something up here.
|
* objects may be freed. It is possible I screwed something up here.
|
||||||
|
@ -792,7 +845,6 @@ delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
|
||||||
PyGC_Head *gc = collectable->gc.gc_next;
|
PyGC_Head *gc = collectable->gc.gc_next;
|
||||||
PyObject *op = FROM_GC(gc);
|
PyObject *op = FROM_GC(gc);
|
||||||
|
|
||||||
assert(IS_TENTATIVELY_UNREACHABLE(op));
|
|
||||||
if (debug & DEBUG_SAVEALL) {
|
if (debug & DEBUG_SAVEALL) {
|
||||||
PyList_Append(garbage, op);
|
PyList_Append(garbage, op);
|
||||||
}
|
}
|
||||||
|
@ -806,7 +858,7 @@ delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
|
||||||
if (collectable->gc.gc_next == gc) {
|
if (collectable->gc.gc_next == gc) {
|
||||||
/* object is still alive, move it, it may die later */
|
/* object is still alive, move it, it may die later */
|
||||||
gc_list_move(gc, old);
|
gc_list_move(gc, old);
|
||||||
gc->gc.gc_refs = GC_REACHABLE;
|
_PyGCHead_SET_REFS(gc, GC_REACHABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -929,19 +981,15 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All objects in unreachable are trash, but objects reachable from
|
/* All objects in unreachable are trash, but objects reachable from
|
||||||
* finalizers can't safely be deleted. Python programmers should take
|
* legacy finalizers (e.g. tp_del) can't safely be deleted.
|
||||||
* care not to create such things. For Python, finalizers means
|
|
||||||
* instance objects with __del__ methods. Weakrefs with callbacks
|
|
||||||
* can also call arbitrary Python code but they will be dealt with by
|
|
||||||
* handle_weakrefs().
|
|
||||||
*/
|
*/
|
||||||
gc_list_init(&finalizers);
|
gc_list_init(&finalizers);
|
||||||
move_finalizers(&unreachable, &finalizers);
|
move_legacy_finalizers(&unreachable, &finalizers);
|
||||||
/* finalizers contains the unreachable objects with a finalizer;
|
/* finalizers contains the unreachable objects with a legacy finalizer;
|
||||||
* unreachable objects reachable *from* those are also uncollectable,
|
* unreachable objects reachable *from* those are also uncollectable,
|
||||||
* and we move those into the finalizers list too.
|
* and we move those into the finalizers list too.
|
||||||
*/
|
*/
|
||||||
move_finalizer_reachable(&finalizers);
|
move_legacy_finalizer_reachable(&finalizers);
|
||||||
|
|
||||||
/* Collect statistics on collectable objects found and print
|
/* Collect statistics on collectable objects found and print
|
||||||
* debugging information.
|
* debugging information.
|
||||||
|
@ -957,11 +1005,20 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
/* Clear weakrefs and invoke callbacks as necessary. */
|
/* Clear weakrefs and invoke callbacks as necessary. */
|
||||||
m += handle_weakrefs(&unreachable, old);
|
m += handle_weakrefs(&unreachable, old);
|
||||||
|
|
||||||
/* Call tp_clear on objects in the unreachable set. This will cause
|
/* Call tp_finalize on objects which have one. */
|
||||||
* the reference cycles to be broken. It may also cause some objects
|
finalize_garbage(&unreachable, old);
|
||||||
* in finalizers to be freed.
|
|
||||||
*/
|
if (check_garbage(&unreachable)) {
|
||||||
delete_garbage(&unreachable, old);
|
revive_garbage(&unreachable);
|
||||||
|
gc_list_merge(&unreachable, old);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Call tp_clear on objects in the unreachable set. This will cause
|
||||||
|
* the reference cycles to be broken. It may also cause some objects
|
||||||
|
* in finalizers to be freed.
|
||||||
|
*/
|
||||||
|
delete_garbage(&unreachable, old);
|
||||||
|
}
|
||||||
|
|
||||||
/* Collect statistics on uncollectable objects found and print
|
/* Collect statistics on uncollectable objects found and print
|
||||||
* debugging information. */
|
* debugging information. */
|
||||||
|
@ -992,7 +1049,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
|
||||||
* reachable list of garbage. The programmer has to deal with
|
* reachable list of garbage. The programmer has to deal with
|
||||||
* this if they insist on creating this type of structure.
|
* this if they insist on creating this type of structure.
|
||||||
*/
|
*/
|
||||||
(void)handle_finalizers(&finalizers, old);
|
(void)handle_legacy_finalizers(&finalizers, old);
|
||||||
|
|
||||||
/* Clear free list only during the collection of the highest
|
/* Clear free list only during the collection of the highest
|
||||||
* generation */
|
* generation */
|
||||||
|
@ -1662,7 +1719,8 @@ _PyObject_GC_Malloc(size_t basicsize)
|
||||||
sizeof(PyGC_Head) + basicsize);
|
sizeof(PyGC_Head) + basicsize);
|
||||||
if (g == NULL)
|
if (g == NULL)
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
g->gc.gc_refs = GC_UNTRACKED;
|
g->gc.gc_refs = 0;
|
||||||
|
_PyGCHead_SET_REFS(g, GC_UNTRACKED);
|
||||||
generations[0].count++; /* number of allocated GC objects */
|
generations[0].count++; /* number of allocated GC objects */
|
||||||
if (generations[0].count > generations[0].threshold &&
|
if (generations[0].count > generations[0].threshold &&
|
||||||
enabled &&
|
enabled &&
|
||||||
|
|
|
@ -15,6 +15,31 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gen_finalize(PyObject *self)
|
||||||
|
{
|
||||||
|
PyGenObject *gen = (PyGenObject *)self;
|
||||||
|
PyObject *res;
|
||||||
|
PyObject *error_type, *error_value, *error_traceback;
|
||||||
|
|
||||||
|
if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL)
|
||||||
|
/* Generator isn't paused, so no need to close */
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Save the current exception, if any. */
|
||||||
|
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||||
|
|
||||||
|
res = gen_close(gen, NULL);
|
||||||
|
|
||||||
|
if (res == NULL)
|
||||||
|
PyErr_WriteUnraisable(self);
|
||||||
|
else
|
||||||
|
Py_DECREF(res);
|
||||||
|
|
||||||
|
/* Restore the saved exception. */
|
||||||
|
PyErr_Restore(error_type, error_value, error_traceback);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gen_dealloc(PyGenObject *gen)
|
gen_dealloc(PyGenObject *gen)
|
||||||
{
|
{
|
||||||
|
@ -27,12 +52,8 @@ gen_dealloc(PyGenObject *gen)
|
||||||
|
|
||||||
_PyObject_GC_TRACK(self);
|
_PyObject_GC_TRACK(self);
|
||||||
|
|
||||||
if (gen->gi_frame != NULL && gen->gi_frame->f_stacktop != NULL) {
|
if (PyObject_CallFinalizerFromDealloc(self))
|
||||||
/* Generator is paused, so we need to close */
|
return; /* resurrected. :( */
|
||||||
Py_TYPE(gen)->tp_del(self);
|
|
||||||
if (self->ob_refcnt > 0)
|
|
||||||
return; /* resurrected. :( */
|
|
||||||
}
|
|
||||||
|
|
||||||
_PyObject_GC_UNTRACK(self);
|
_PyObject_GC_UNTRACK(self);
|
||||||
Py_CLEAR(gen->gi_frame);
|
Py_CLEAR(gen->gi_frame);
|
||||||
|
@ -40,7 +61,6 @@ gen_dealloc(PyGenObject *gen)
|
||||||
PyObject_GC_Del(gen);
|
PyObject_GC_Del(gen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
|
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
|
||||||
{
|
{
|
||||||
|
@ -222,68 +242,6 @@ gen_close(PyGenObject *gen, PyObject *args)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
gen_del(PyObject *self)
|
|
||||||
{
|
|
||||||
PyObject *res;
|
|
||||||
PyObject *error_type, *error_value, *error_traceback;
|
|
||||||
PyGenObject *gen = (PyGenObject *)self;
|
|
||||||
|
|
||||||
if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL)
|
|
||||||
/* Generator isn't paused, so no need to close */
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Temporarily resurrect the object. */
|
|
||||||
assert(self->ob_refcnt == 0);
|
|
||||||
self->ob_refcnt = 1;
|
|
||||||
|
|
||||||
/* Save the current exception, if any. */
|
|
||||||
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
|
||||||
|
|
||||||
res = gen_close(gen, NULL);
|
|
||||||
|
|
||||||
if (res == NULL)
|
|
||||||
PyErr_WriteUnraisable(self);
|
|
||||||
else
|
|
||||||
Py_DECREF(res);
|
|
||||||
|
|
||||||
/* Restore the saved exception. */
|
|
||||||
PyErr_Restore(error_type, error_value, error_traceback);
|
|
||||||
|
|
||||||
/* Undo the temporary resurrection; can't use DECREF here, it would
|
|
||||||
* cause a recursive call.
|
|
||||||
*/
|
|
||||||
assert(self->ob_refcnt > 0);
|
|
||||||
if (--self->ob_refcnt == 0)
|
|
||||||
return; /* this is the normal path out */
|
|
||||||
|
|
||||||
/* close() resurrected it! Make it look like the original Py_DECREF
|
|
||||||
* never happened.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
Py_ssize_t refcnt = self->ob_refcnt;
|
|
||||||
_Py_NewReference(self);
|
|
||||||
self->ob_refcnt = refcnt;
|
|
||||||
}
|
|
||||||
assert(PyType_IS_GC(Py_TYPE(self)) &&
|
|
||||||
_Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
|
|
||||||
|
|
||||||
/* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
|
|
||||||
* we need to undo that. */
|
|
||||||
_Py_DEC_REFTOTAL;
|
|
||||||
/* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
|
|
||||||
* chain, so no more to do there.
|
|
||||||
* If COUNT_ALLOCS, the original decref bumped tp_frees, and
|
|
||||||
* _Py_NewReference bumped tp_allocs: both of those need to be
|
|
||||||
* undone.
|
|
||||||
*/
|
|
||||||
#ifdef COUNT_ALLOCS
|
|
||||||
--(Py_TYPE(self)->tp_frees);
|
|
||||||
--(Py_TYPE(self)->tp_allocs);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PyDoc_STRVAR(throw_doc,
|
PyDoc_STRVAR(throw_doc,
|
||||||
"throw(typ[,val[,tb]]) -> raise exception in generator,\n\
|
"throw(typ[,val[,tb]]) -> raise exception in generator,\n\
|
||||||
|
@ -517,7 +475,8 @@ PyTypeObject PyGen_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
0, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
|
||||||
|
Py_TPFLAGS_HAVE_FINALIZE, /* tp_flags */
|
||||||
0, /* tp_doc */
|
0, /* tp_doc */
|
||||||
(traverseproc)gen_traverse, /* tp_traverse */
|
(traverseproc)gen_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
|
@ -544,7 +503,9 @@ PyTypeObject PyGen_Type = {
|
||||||
0, /* tp_cache */
|
0, /* tp_cache */
|
||||||
0, /* tp_subclasses */
|
0, /* tp_subclasses */
|
||||||
0, /* tp_weaklist */
|
0, /* tp_weaklist */
|
||||||
gen_del, /* tp_del */
|
0, /* tp_del */
|
||||||
|
0, /* tp_version_tag */
|
||||||
|
gen_finalize, /* tp_finalize */
|
||||||
};
|
};
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
|
|
|
@ -255,6 +255,72 @@ _PyObject_NewVar(PyTypeObject *tp, Py_ssize_t nitems)
|
||||||
return PyObject_INIT_VAR(op, tp, nitems);
|
return PyObject_INIT_VAR(op, tp, nitems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PyObject_CallFinalizer(PyObject *self)
|
||||||
|
{
|
||||||
|
PyTypeObject *tp = Py_TYPE(self);
|
||||||
|
|
||||||
|
/* The former could happen on heaptypes created from the C API, e.g.
|
||||||
|
PyType_FromSpec(). */
|
||||||
|
if (!PyType_HasFeature(tp, Py_TPFLAGS_HAVE_FINALIZE) ||
|
||||||
|
tp->tp_finalize == NULL)
|
||||||
|
return;
|
||||||
|
/* tp_finalize should only be called once. */
|
||||||
|
if (PyType_IS_GC(tp) && _PyGC_FINALIZED(self))
|
||||||
|
return;
|
||||||
|
|
||||||
|
tp->tp_finalize(self);
|
||||||
|
if (PyType_IS_GC(tp))
|
||||||
|
_PyGC_SET_FINALIZED(self, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PyObject_CallFinalizerFromDealloc(PyObject *self)
|
||||||
|
{
|
||||||
|
Py_ssize_t refcnt;
|
||||||
|
|
||||||
|
/* Temporarily resurrect the object. */
|
||||||
|
if (self->ob_refcnt != 0) {
|
||||||
|
Py_FatalError("PyObject_CallFinalizerFromDealloc called on "
|
||||||
|
"object with a non-zero refcount");
|
||||||
|
}
|
||||||
|
self->ob_refcnt = 1;
|
||||||
|
|
||||||
|
PyObject_CallFinalizer(self);
|
||||||
|
|
||||||
|
/* Undo the temporary resurrection; can't use DECREF here, it would
|
||||||
|
* cause a recursive call.
|
||||||
|
*/
|
||||||
|
assert(self->ob_refcnt > 0);
|
||||||
|
if (--self->ob_refcnt == 0)
|
||||||
|
return 0; /* this is the normal path out */
|
||||||
|
|
||||||
|
/* tp_finalize resurrected it! Make it look like the original Py_DECREF
|
||||||
|
* never happened.
|
||||||
|
*/
|
||||||
|
refcnt = self->ob_refcnt;
|
||||||
|
_Py_NewReference(self);
|
||||||
|
self->ob_refcnt = refcnt;
|
||||||
|
|
||||||
|
if (PyType_IS_GC(Py_TYPE(self))) {
|
||||||
|
assert(_PyGC_REFS(self) != _PyGC_REFS_UNTRACKED);
|
||||||
|
}
|
||||||
|
/* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
|
||||||
|
* we need to undo that. */
|
||||||
|
_Py_DEC_REFTOTAL;
|
||||||
|
/* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
|
||||||
|
* chain, so no more to do there.
|
||||||
|
* If COUNT_ALLOCS, the original decref bumped tp_frees, and
|
||||||
|
* _Py_NewReference bumped tp_allocs: both of those need to be
|
||||||
|
* undone.
|
||||||
|
*/
|
||||||
|
#ifdef COUNT_ALLOCS
|
||||||
|
--Py_TYPE(self)->tp_frees;
|
||||||
|
--Py_TYPE(self)->tp_allocs;
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
PyObject_Print(PyObject *op, FILE *fp, int flags)
|
PyObject_Print(PyObject *op, FILE *fp, int flags)
|
||||||
{
|
{
|
||||||
|
@ -1981,7 +2047,7 @@ void
|
||||||
_PyTrash_deposit_object(PyObject *op)
|
_PyTrash_deposit_object(PyObject *op)
|
||||||
{
|
{
|
||||||
assert(PyObject_IS_GC(op));
|
assert(PyObject_IS_GC(op));
|
||||||
assert(_Py_AS_GC(op)->gc.gc_refs == _PyGC_REFS_UNTRACKED);
|
assert(_PyGC_REFS(op) == _PyGC_REFS_UNTRACKED);
|
||||||
assert(op->ob_refcnt == 0);
|
assert(op->ob_refcnt == 0);
|
||||||
_Py_AS_GC(op)->gc.gc_prev = (PyGC_Head *)_PyTrash_delete_later;
|
_Py_AS_GC(op)->gc.gc_prev = (PyGC_Head *)_PyTrash_delete_later;
|
||||||
_PyTrash_delete_later = op;
|
_PyTrash_delete_later = op;
|
||||||
|
@ -1993,7 +2059,7 @@ _PyTrash_thread_deposit_object(PyObject *op)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = PyThreadState_GET();
|
PyThreadState *tstate = PyThreadState_GET();
|
||||||
assert(PyObject_IS_GC(op));
|
assert(PyObject_IS_GC(op));
|
||||||
assert(_Py_AS_GC(op)->gc.gc_refs == _PyGC_REFS_UNTRACKED);
|
assert(_PyGC_REFS(op) == _PyGC_REFS_UNTRACKED);
|
||||||
assert(op->ob_refcnt == 0);
|
assert(op->ob_refcnt == 0);
|
||||||
_Py_AS_GC(op)->gc.gc_prev = (PyGC_Head *) tstate->trash_delete_later;
|
_Py_AS_GC(op)->gc.gc_prev = (PyGC_Head *) tstate->trash_delete_later;
|
||||||
tstate->trash_delete_later = op;
|
tstate->trash_delete_later = op;
|
||||||
|
|
|
@ -921,6 +921,7 @@ subtype_dealloc(PyObject *self)
|
||||||
PyTypeObject *type, *base;
|
PyTypeObject *type, *base;
|
||||||
destructor basedealloc;
|
destructor basedealloc;
|
||||||
PyThreadState *tstate = PyThreadState_GET();
|
PyThreadState *tstate = PyThreadState_GET();
|
||||||
|
int has_finalizer;
|
||||||
|
|
||||||
/* Extract the type; we expect it to be a heap type */
|
/* Extract the type; we expect it to be a heap type */
|
||||||
type = Py_TYPE(self);
|
type = Py_TYPE(self);
|
||||||
|
@ -936,6 +937,10 @@ subtype_dealloc(PyObject *self)
|
||||||
clear_slots(), or DECREF the dict, or clear weakrefs. */
|
clear_slots(), or DECREF the dict, or clear weakrefs. */
|
||||||
|
|
||||||
/* Maybe call finalizer; exit early if resurrected */
|
/* Maybe call finalizer; exit early if resurrected */
|
||||||
|
if (type->tp_finalize) {
|
||||||
|
if (PyObject_CallFinalizerFromDealloc(self) < 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (type->tp_del) {
|
if (type->tp_del) {
|
||||||
type->tp_del(self);
|
type->tp_del(self);
|
||||||
if (self->ob_refcnt > 0)
|
if (self->ob_refcnt > 0)
|
||||||
|
@ -987,25 +992,36 @@ subtype_dealloc(PyObject *self)
|
||||||
assert(base);
|
assert(base);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we added a weaklist, we clear it. Do this *before* calling
|
has_finalizer = type->tp_finalize || type->tp_del;
|
||||||
the finalizer (__del__), clearing slots, or clearing the instance
|
|
||||||
dict. */
|
|
||||||
|
|
||||||
|
/* Maybe call finalizer; exit early if resurrected */
|
||||||
|
if (has_finalizer)
|
||||||
|
_PyObject_GC_TRACK(self);
|
||||||
|
|
||||||
|
if (type->tp_finalize) {
|
||||||
|
if (PyObject_CallFinalizerFromDealloc(self) < 0) {
|
||||||
|
/* Resurrected */
|
||||||
|
goto endlabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* If we added a weaklist, we clear it. Do this *before* calling
|
||||||
|
tp_del, clearing slots, or clearing the instance dict. */
|
||||||
if (type->tp_weaklistoffset && !base->tp_weaklistoffset)
|
if (type->tp_weaklistoffset && !base->tp_weaklistoffset)
|
||||||
PyObject_ClearWeakRefs(self);
|
PyObject_ClearWeakRefs(self);
|
||||||
|
|
||||||
/* Maybe call finalizer; exit early if resurrected */
|
|
||||||
if (type->tp_del) {
|
if (type->tp_del) {
|
||||||
_PyObject_GC_TRACK(self);
|
|
||||||
type->tp_del(self);
|
type->tp_del(self);
|
||||||
if (self->ob_refcnt > 0)
|
if (self->ob_refcnt > 0) {
|
||||||
goto endlabel; /* resurrected */
|
/* Resurrected */
|
||||||
else
|
goto endlabel;
|
||||||
_PyObject_GC_UNTRACK(self);
|
}
|
||||||
|
}
|
||||||
|
if (has_finalizer) {
|
||||||
|
_PyObject_GC_UNTRACK(self);
|
||||||
/* New weakrefs could be created during the finalizer call.
|
/* New weakrefs could be created during the finalizer call.
|
||||||
If this occurs, clear them out without calling their
|
If this occurs, clear them out without calling their
|
||||||
finalizers since they might rely on part of the object
|
finalizers since they might rely on part of the object
|
||||||
being finalized that has already been destroyed. */
|
being finalized that has already been destroyed. */
|
||||||
if (type->tp_weaklistoffset && !base->tp_weaklistoffset) {
|
if (type->tp_weaklistoffset && !base->tp_weaklistoffset) {
|
||||||
/* Modeled after GET_WEAKREFS_LISTPTR() */
|
/* Modeled after GET_WEAKREFS_LISTPTR() */
|
||||||
PyWeakReference **list = (PyWeakReference **) \
|
PyWeakReference **list = (PyWeakReference **) \
|
||||||
|
@ -2231,7 +2247,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
|
|
||||||
/* Initialize tp_flags */
|
/* Initialize tp_flags */
|
||||||
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
|
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
|
||||||
Py_TPFLAGS_BASETYPE;
|
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_FINALIZE;
|
||||||
if (base->tp_flags & Py_TPFLAGS_HAVE_GC)
|
if (base->tp_flags & Py_TPFLAGS_HAVE_GC)
|
||||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
||||||
|
|
||||||
|
@ -4111,6 +4127,10 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
|
||||||
COPYSLOT(tp_init);
|
COPYSLOT(tp_init);
|
||||||
COPYSLOT(tp_alloc);
|
COPYSLOT(tp_alloc);
|
||||||
COPYSLOT(tp_is_gc);
|
COPYSLOT(tp_is_gc);
|
||||||
|
if ((type->tp_flags & Py_TPFLAGS_HAVE_FINALIZE) &&
|
||||||
|
(base->tp_flags & Py_TPFLAGS_HAVE_FINALIZE)) {
|
||||||
|
COPYSLOT(tp_finalize);
|
||||||
|
}
|
||||||
if ((type->tp_flags & Py_TPFLAGS_HAVE_GC) ==
|
if ((type->tp_flags & Py_TPFLAGS_HAVE_GC) ==
|
||||||
(base->tp_flags & Py_TPFLAGS_HAVE_GC)) {
|
(base->tp_flags & Py_TPFLAGS_HAVE_GC)) {
|
||||||
/* They agree about gc. */
|
/* They agree about gc. */
|
||||||
|
@ -4736,6 +4756,18 @@ wrap_call(PyObject *self, PyObject *args, void *wrapped, PyObject *kwds)
|
||||||
return (*func)(self, args, kwds);
|
return (*func)(self, args, kwds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
wrap_del(PyObject *self, PyObject *args, void *wrapped)
|
||||||
|
{
|
||||||
|
destructor func = (destructor)wrapped;
|
||||||
|
|
||||||
|
if (!check_num_args(args, 0))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
(*func)(self);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
wrap_richcmpfunc(PyObject *self, PyObject *args, void *wrapped, int op)
|
wrap_richcmpfunc(PyObject *self, PyObject *args, void *wrapped, int op)
|
||||||
{
|
{
|
||||||
|
@ -5617,16 +5649,12 @@ slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
slot_tp_del(PyObject *self)
|
slot_tp_finalize(PyObject *self)
|
||||||
{
|
{
|
||||||
_Py_IDENTIFIER(__del__);
|
_Py_IDENTIFIER(__del__);
|
||||||
PyObject *del, *res;
|
PyObject *del, *res;
|
||||||
PyObject *error_type, *error_value, *error_traceback;
|
PyObject *error_type, *error_value, *error_traceback;
|
||||||
|
|
||||||
/* Temporarily resurrect the object. */
|
|
||||||
assert(self->ob_refcnt == 0);
|
|
||||||
self->ob_refcnt = 1;
|
|
||||||
|
|
||||||
/* Save the current exception, if any. */
|
/* Save the current exception, if any. */
|
||||||
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||||
|
|
||||||
|
@ -5643,37 +5671,6 @@ slot_tp_del(PyObject *self)
|
||||||
|
|
||||||
/* Restore the saved exception. */
|
/* Restore the saved exception. */
|
||||||
PyErr_Restore(error_type, error_value, error_traceback);
|
PyErr_Restore(error_type, error_value, error_traceback);
|
||||||
|
|
||||||
/* Undo the temporary resurrection; can't use DECREF here, it would
|
|
||||||
* cause a recursive call.
|
|
||||||
*/
|
|
||||||
assert(self->ob_refcnt > 0);
|
|
||||||
if (--self->ob_refcnt == 0)
|
|
||||||
return; /* this is the normal path out */
|
|
||||||
|
|
||||||
/* __del__ resurrected it! Make it look like the original Py_DECREF
|
|
||||||
* never happened.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
Py_ssize_t refcnt = self->ob_refcnt;
|
|
||||||
_Py_NewReference(self);
|
|
||||||
self->ob_refcnt = refcnt;
|
|
||||||
}
|
|
||||||
assert(!PyType_IS_GC(Py_TYPE(self)) ||
|
|
||||||
_Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
|
|
||||||
/* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
|
|
||||||
* we need to undo that. */
|
|
||||||
_Py_DEC_REFTOTAL;
|
|
||||||
/* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
|
|
||||||
* chain, so no more to do there.
|
|
||||||
* If COUNT_ALLOCS, the original decref bumped tp_frees, and
|
|
||||||
* _Py_NewReference bumped tp_allocs: both of those need to be
|
|
||||||
* undone.
|
|
||||||
*/
|
|
||||||
#ifdef COUNT_ALLOCS
|
|
||||||
--Py_TYPE(self)->tp_frees;
|
|
||||||
--Py_TYPE(self)->tp_allocs;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5782,7 +5779,7 @@ static slotdef slotdefs[] = {
|
||||||
"see help(type(x)) for signature",
|
"see help(type(x)) for signature",
|
||||||
PyWrapperFlag_KEYWORDS),
|
PyWrapperFlag_KEYWORDS),
|
||||||
TPSLOT("__new__", tp_new, slot_tp_new, NULL, ""),
|
TPSLOT("__new__", tp_new, slot_tp_new, NULL, ""),
|
||||||
TPSLOT("__del__", tp_del, slot_tp_del, NULL, ""),
|
TPSLOT("__del__", tp_finalize, slot_tp_finalize, (wrapperfunc)wrap_del, ""),
|
||||||
|
|
||||||
BINSLOT("__add__", nb_add, slot_nb_add,
|
BINSLOT("__add__", nb_add, slot_nb_add,
|
||||||
"+"),
|
"+"),
|
||||||
|
|
Loading…
Reference in New Issue