summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2020-09-30 10:40:14 -0500
committerJason Madden <jamadden@gmail.com>2020-09-30 10:40:14 -0500
commit8cf31ebd6be86332860294cb541f8978d138d3b4 (patch)
tree51b270702e835b41d020e3f4bb923bd6b99ad44b
parentb749fc0f17ee628b9ef7bb8afd14b6bf51414bff (diff)
downloadzope-interface-8cf31ebd6be86332860294cb541f8978d138d3b4.tar.gz
Add info on the interaction of weakrefs and interface hashing.issue216
-rw-r--r--CHANGES.rst21
-rw-r--r--docs/api/specifications.rst64
2 files changed, 85 insertions, 0 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index e7ed90d..ab01fd6 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -19,6 +19,27 @@
garbage collection times. See `issue 216
<https://github.com/zopefoundation/zope.interface/issues/216>`_.
+ .. caution::
+
+ This leak could prevent interfaces used as the bases of
+ other interfaces from being garbage collected. Those interfaces
+ will now be collected.
+
+ One way in which this would manifest was that ``weakref.ref``
+ objects (and things built upon them, like
+ ``Weak[Key|Value]Dictionary``) would continue to have access to
+ the original object even if there were no other visible
+ references to Python and the original object *should* have been
+ collected. This could be especially problematic for the
+ ``WeakKeyDictionary`` when combined with dynamic or local
+ (created in the scope of a function) interfaces, since interfaces
+ are hashed based just on their name and module name. See the
+ linked issue for an example of a resulting ``KeyError``.
+
+ Note that such potential errors are not new, they are just once
+ again a possibility.
+
+
5.1.0 (2020-04-08)
==================
diff --git a/docs/api/specifications.rst b/docs/api/specifications.rst
index 357e361..45cf5e1 100644
--- a/docs/api/specifications.rst
+++ b/docs/api/specifications.rst
@@ -161,6 +161,70 @@ Exmples for :meth:`.Specification.extends`:
>>> I2.extends(I2, strict=False)
True
+Equality, Hashing, and Comparisons
+----------------------------------
+
+Specifications (including their notable subclass `Interface`), are
+hashed and compared based solely on their ``__name__`` and
+``__module__``, not including any information about their enclosing
+scope, if any (e.g., their ``__qualname__``). This means that any two
+objects created with the same name and module are considered equal and
+map to the same value in a dictionary.
+
+.. doctest::
+
+ >>> from zope.interface import Interface
+ >>> class I1(Interface): pass
+ >>> orig_I1 = I1
+ >>> class I1(Interface): pass
+ >>> I1 is orig_I1
+ False
+ >>> I1 == orig_I1
+ True
+ >>> d = {I1: 42}
+ >>> d[orig_I1]
+ 42
+ >>> def make_nested():
+ ... class I1(Interface): pass
+ ... return I1
+ >>> nested_I1 = make_nested()
+ >>> I1 == orig_I1 == nested_I1
+ True
+
+Because weak references hash the same as their underlying object,
+this can lead to surprising results when weak references are involved,
+especially if there are cycles involved or if the garbage collector is
+not based on reference counting (e.g., PyPy). For example, if you
+redefine an interface named the same as an interface being used in a
+``WeakKeyDictionary``, you can get a ``KeyError``, even if you put the
+new interface into the dictionary.
+
+
+.. doctest::
+
+ >>> from zope.interface import Interface
+ >>> import gc
+ >>> from weakref import WeakKeyDictionary
+ >>> wr_dict = WeakKeyDictionary()
+ >>> class I1(Interface): pass
+ >>> wr_dict[I1] = 42
+ >>> orig_I1 = I1 # Make sure it stays alive
+ >>> class I1(Interface): pass
+ >>> wr_dict[I1] = 2020
+ >>> del orig_I1
+ >>> _ = gc.collect() # Sometime later, gc runs and makes sure the original is gone
+ >>> wr_dict[I1] # Cleaning up the original weakref removed the new one
+ Traceback (most recent call last):
+ ...
+ KeyError: ...
+
+This is mostly likely a problem in test cases where it is tempting to
+use the same named interfaces in different test methods. If references
+to them escape, especially if they are used as the bases of other
+interfaces, you may find surprising ``KeyError`` exceptions. For this
+reason, it is best to use distinct names for local interfaces within
+the same test module.
+
Interface
=========