Skip to content

Commit

Permalink
C optimizations: Spec_clear and Spec_traverse need to include Spec->_…
Browse files Browse the repository at this point in the history
…bases

Previously they did not, leading to a reference leak of a tuple.

Fixes #216
  • Loading branch information
jamadden committed Sep 28, 2020
1 parent 255db9d commit b749fc0
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
==================
Expand Down
2 changes: 2 additions & 0 deletions src/zope/interface/_zope_interface_coptimizations.c
Original file line number Diff line number Diff line change
Expand Up @@ -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__);
Expand All @@ -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__);
Expand Down
29 changes: 29 additions & 0 deletions src/zope/interface/tests/test_declarations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,35 @@ class Foo(object):
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):
Expand Down

0 comments on commit b749fc0

Please sign in to comment.