C optimizations: Spec_clear and Spec_traverse need to include Spec->_bases
Previously they did not, leading to a reference leak of a tuple. Fixes #216
This commit is contained in:
parent
255db9d3c0
commit
b749fc0f17
|
@ -12,6 +12,12 @@
|
|||
that argument. See `issue 208
|
||||
<https://github.com/zopefoundation/zope.interface/issues/208>`_.
|
||||
|
||||
- Fix a potential reference leak in the C optimizations. Previously,
|
||||
applications that dynamically created unique ``Specification``
|
||||
objects (e.g., used ``@implementer`` on dynamic classes) could
|
||||
notice a growth of small objects over time leading to increased
|
||||
garbage collection times. See `issue 216
|
||||
<https://github.com/zopefoundation/zope.interface/issues/216>`_.
|
||||
|
||||
5.1.0 (2020-04-08)
|
||||
==================
|
||||
|
|
|
@ -350,6 +350,7 @@ Spec_traverse(Spec* self, visitproc visit, void* arg)
|
|||
{
|
||||
Py_VISIT(self->_implied);
|
||||
Py_VISIT(self->_dependents);
|
||||
Py_VISIT(self->_bases);
|
||||
Py_VISIT(self->_v_attrs);
|
||||
Py_VISIT(self->__iro__);
|
||||
Py_VISIT(self->__sro__);
|
||||
|
@ -361,6 +362,7 @@ Spec_clear(Spec* self)
|
|||
{
|
||||
Py_CLEAR(self->_implied);
|
||||
Py_CLEAR(self->_dependents);
|
||||
Py_CLEAR(self->_bases);
|
||||
Py_CLEAR(self->_v_attrs);
|
||||
Py_CLEAR(self->__iro__);
|
||||
Py_CLEAR(self->__sro__);
|
||||
|
|
|
@ -1078,6 +1078,35 @@ class Test_implementer(Test_classImplements):
|
|||
self.assertIsNone(spec.inherit,)
|
||||
self.assertIs(foo.__implemented__, spec) # pylint:disable=no-member
|
||||
|
||||
def test_does_not_leak_on_unique_classes(self):
|
||||
# Make sure nothing is hanging on to the class or Implements
|
||||
# object after they go out of scope. There was briefly a bug
|
||||
# in 5.x that caused SpecificationBase._bases (in C) to not be
|
||||
# traversed or cleared.
|
||||
# https://github.com/zopefoundation/zope.interface/issues/216
|
||||
import gc
|
||||
from zope.interface.interface import InterfaceClass
|
||||
IFoo = InterfaceClass('IFoo')
|
||||
|
||||
begin_count = len(gc.get_objects())
|
||||
|
||||
for _ in range(1900):
|
||||
class TestClass(object):
|
||||
pass
|
||||
|
||||
self._callFUT(TestClass, IFoo)
|
||||
|
||||
gc.collect()
|
||||
|
||||
end_count = len(gc.get_objects())
|
||||
|
||||
# How many new objects might still be around? In all currently
|
||||
# tested interpreters, there aren't any, so our counts should
|
||||
# match exactly. When the bug existed, in a steady state, the loop
|
||||
# would grow by two objects each iteration
|
||||
fudge_factor = 0
|
||||
self.assertLessEqual(end_count, begin_count + fudge_factor)
|
||||
|
||||
|
||||
|
||||
class Test_implementer_only(Test_classImplementsOnly):
|
||||
|
|
Loading…
Reference in New Issue