diff options
author | Jason Madden <jamadden@gmail.com> | 2020-09-30 10:40:14 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2020-09-30 10:40:14 -0500 |
commit | 8cf31ebd6be86332860294cb541f8978d138d3b4 (patch) | |
tree | 51b270702e835b41d020e3f4bb923bd6b99ad44b | |
parent | b749fc0f17ee628b9ef7bb8afd14b6bf51414bff (diff) | |
download | zope-interface-8cf31ebd6be86332860294cb541f8978d138d3b4.tar.gz |
Add info on the interaction of weakrefs and interface hashing.issue216
-rw-r--r-- | CHANGES.rst | 21 | ||||
-rw-r--r-- | docs/api/specifications.rst | 64 |
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 ========= |