[Checkins] SVN: zope.component/trunk/ Fixed 2 related bugs:

Jim Fulton jim at zope.com
Tue Jun 2 15:44:19 EDT 2009


Log message for revision 100603:
  Fixed 2 related bugs:
  
  When a utility is registered and there was previously a utility
  registered for the same interface and name, then the old utility is
  unregistered.  The 2 bugs related to this:
  
  - There was no ``Unregistered`` for the implicit unregistration. Now
    there is.
  
  - The old utility was still held and returned by
    getAllUtilitiesRegisteredFor.  In other words, it was still
    considered registered, eeven though it wasn't.  A particularly
    negative consequence of this is that the utility is held in memory
    or in the database even though it isn't used.
  

Changed:
  U   zope.component/trunk/CHANGES.txt
  U   zope.component/trunk/src/zope/component/registry.py
  U   zope.component/trunk/src/zope/component/registry.txt
  U   zope.component/trunk/src/zope/component/tests.py

-=-
Modified: zope.component/trunk/CHANGES.txt
===================================================================
--- zope.component/trunk/CHANGES.txt	2009-06-02 18:44:04 UTC (rev 100602)
+++ zope.component/trunk/CHANGES.txt	2009-06-02 19:44:19 UTC (rev 100603)
@@ -4,9 +4,21 @@
 3.7.1 (unreleased)
 ==================
 
-- Nothing changed yet.
+- Fixed 2 related bugs:
 
+  When a utility is registered and there was previously a utility
+  registered for the same interface and name, then the old utility is
+  unregistered.  The 2 bugs related to this:
 
+  - There was no ``Unregistered`` for the implicit unregistration. Now
+    there is.
+
+  - The old utility was still held and returned by
+    getAllUtilitiesRegisteredFor.  In other words, it was still
+    considered registered, eeven though it wasn't.  A particularly
+    negative consequence of this is that the utility is held in memory
+    or in the database even though it isn't used.
+
 3.7.0 (2009-05-21)
 ==================
 
@@ -14,7 +26,7 @@
 
 - Add in zope:view and zope:resource implementations into
   zope.component.zcml (dependency loaded with zope.component [zcml]).
- 
+
 3.6.0 (2009-03-12)
 ==================
 
@@ -24,7 +36,7 @@
   framework were still using those interfaces. They will be adapted
   for this change. If you were using some of those interfaces, you
   need to adapt your code as well:
-  
+
    - The IView and IDefaultViewName were moved to zope.publisher.interfaces.
 
    - The IResource was moved to zope.app.publisher.interfaces.

Modified: zope.component/trunk/src/zope/component/registry.py
===================================================================
--- zope.component/trunk/src/zope/component/registry.py	2009-06-02 18:44:04 UTC (rev 100602)
+++ zope.component/trunk/src/zope/component/registry.py	2009-06-02 19:44:19 UTC (rev 100603)
@@ -91,9 +91,11 @@
             provided = _getUtilityProvided(component)
 
         reg = self._utility_registrations.get((provided, name))
-        if reg is not None and reg[:2] == (component, info):
-            # already registered
-            return
+        if reg is not None:
+            if reg[:2] == (component, info):
+                # already registered
+                return
+            self.unregisterUtility(reg[0], provided, name)
 
         subscribed = False
         for ((p, _), data) in self._utility_registrations.iteritems():
@@ -109,10 +111,12 @@
 
         if event:
             notify(Registered(
-                UtilityRegistration(self, provided, name, component, info, factory)
+                UtilityRegistration(self, provided, name, component, info,
+                                    factory)
                 ))
 
-    def unregisterUtility(self, component=None, provided=None, name=u'', factory=None):
+    def unregisterUtility(self, component=None, provided=None, name=u'',
+                          factory=None):
         if factory:
             if component:
                 raise TypeError("Can't specify factory and component.")
@@ -120,7 +124,8 @@
 
         if provided is None:
             if component is None:
-                raise TypeError("Must specify one of component, factory and provided")
+                raise TypeError("Must specify one of component, factory and "
+                                "provided")
             provided = _getUtilityProvided(component)
 
         old = self._utility_registrations.get((provided, name))
@@ -130,6 +135,9 @@
 
         if component is None:
             component = old[0]
+
+        # Note that component is now the old thing registered
+
         del self._utility_registrations[(provided, name)]
         self.utilities.unregister((), provided, name)
 
@@ -436,7 +444,7 @@
                 getattr(self.component, '__name__', `self.component`),
                 self.factory, self.info,
                 )
-        
+
     def __cmp__(self, other):
         return cmp(self.__repr__(), other.__repr__())
 

Modified: zope.component/trunk/src/zope/component/registry.txt
===================================================================
--- zope.component/trunk/src/zope/component/registry.txt	2009-06-02 18:44:04 UTC (rev 100602)
+++ zope.component/trunk/src/zope/component/registry.txt	2009-06-02 19:44:19 UTC (rev 100603)
@@ -49,6 +49,8 @@
     >>> def factory():
     ...    return tests.U1(1)
     >>> components.registerUtility(factory=factory)
+    Unregistered event:
+    UtilityRegistration(<Components comps>, I1, u'', 1, None, u'')
     Registered event:
     UtilityRegistration(<Components comps>, I1, u'', 1, <function factory at <SOME ADDRESS>>, u'')
 
@@ -121,6 +123,8 @@
 Duplicate registrations replace existing ones:
 
     >>> components.registerUtility(tests.U1(4), info=u'use 4 now')
+    Unregistered event:
+    UtilityRegistration(<Components comps>, I1, u'', 1, <function factory at <SOME ADDRESS>>, u'')
     Registered event:
     UtilityRegistration(<Components comps>, I1, u'', 4, None, u'use 4 now')
     >>> components.getUtility(tests.I1)
@@ -926,6 +930,8 @@
     >>> c2.queryUtility(tests.I1)
     U1(1)
     >>> c1.registerUtility(tests.U1(2))
+    Unregistered event:
+    UtilityRegistration(<Components 1>, I1, u'', 1, None, u'')
     Registered event:
     UtilityRegistration(<Components 1>, I1, u'', 2, None, u'')
 

Modified: zope.component/trunk/src/zope/component/tests.py
===================================================================
--- zope.component/trunk/src/zope/component/tests.py	2009-06-02 18:44:04 UTC (rev 100602)
+++ zope.component/trunk/src/zope/component/tests.py	2009-06-02 19:44:19 UTC (rev 100603)
@@ -88,11 +88,11 @@
 
     def __repr__(self):
         return "%s%r" % (self.__class__.__name__, self.context)
-    
+
 class A12_1(A):
     component.adapts(I1, I2)
     interface.implements(IA1)
-    
+
 class A12_(A):
     component.adapts(I1, I2)
 
@@ -712,7 +712,7 @@
 
 def test_persistent_component_managers():
     """
-Here, we'll demonstrate that changes work even when data are stored in 
+Here, we'll demonstrate that changes work even when data are stored in
 a database and when accessed from multiple connections.
 
 Start by setting up a database and creating two transaction
@@ -729,7 +729,7 @@
     >>> r2 = c2.root()
 
 Create a set of components registries in the database, alternating
-connections. 
+connections.
 
     >>> from zope.component.persistentregistry import PersistentComponents
 
@@ -761,7 +761,7 @@
     >>> r1[2].queryUtility(I1)
     U1(1)
     >>> t1.commit()
-    
+
     >>> _ = t2.begin()
     >>> r2[1].registerUtility(U1(2))
     >>> r2[2].queryUtility(I1)
@@ -771,7 +771,7 @@
     U1(2)
     >>> t2.commit()
 
-    
+
     >>> _ = t1.begin()
     >>> r1[1].registerUtility(U12(1), I2)
     >>> r1[4].queryUtility(I2)
@@ -789,9 +789,9 @@
 
     >>> r1[1].registerHandler(handle1, info="First handler")
     >>> r1[2].registerHandler(handle, required=[U])
-    
+
     >>> r1[3].registerHandler(handle3)
-    
+
     >>> r1[4].registerHandler(handle4)
 
     >>> r1[4].handle(U1(1))
@@ -858,7 +858,7 @@
 GlobalRegistry.adapters = base
 def clear_base():
     base.__init__(GlobalRegistry, 'adapters')
-    
+
 class IFoo(interface.Interface):
     pass
 class Foo(persistent.Persistent):
@@ -872,12 +872,12 @@
 
 def test_deghostification_of_persistent_adapter_registries():
     """
-    
+
 We want to make sure that we see updates corrextly.
 
     >>> len(base._v_subregistries)
     0
-    
+
     >>> import ZODB.tests.util
     >>> db = ZODB.tests.util.DB()
     >>> tm1 = transaction.TransactionManager()
@@ -931,15 +931,17 @@
 
 
 def test_multi_handler_unregistration():
-    """There was a bug where multiple handlers for the same required specification
-    would all be removed when one of them was unregistered:
+    """
+    There was a bug where multiple handlers for the same required
+    specification would all be removed when one of them was
+    unregistered:
 
     >>> class I(zope.interface.Interface):
     ...     pass
     >>> def factory1(event):
-    ...     print "| Factory 1 is here" 
+    ...     print "| Factory 1 is here"
     >>> def factory2(event):
-    ...     print "| Factory 2 is here" 
+    ...     print "| Factory 2 is here"
     >>> class Event(object):
     ...     zope.interface.implements(I)
     >>> from zope.component.registry import Components
@@ -960,11 +962,11 @@
     It is common for a utility to delegate its answer to a utility
     providing the same interface in one of the component registry's
     bases. Let's first create a global utility::
-    
+
       >>> import zope.interface
       >>> class IMyUtility(zope.interface.Interface):
       ...     pass
-    
+
       >>> class MyUtility(ConformsToIComponentLookup):
       ...     zope.interface.implements(IMyUtility)
       ...     def __init__(self, id, sm):
@@ -975,18 +977,18 @@
 
       >>> from zope.component import getGlobalSiteManager
       >>> gsm = getGlobalSiteManager()
-    
+
       >>> gutil = MyUtility('global', gsm)
       >>> gsm.registerUtility(gutil, IMyUtility, 'myutil')
-    
+
     Now, let's create two registries and set up the bases hierarchy::
-    
+
       >>> from zope.component.registry import Components
       >>> sm1 = Components('sm1', bases=(gsm, ))
       >>> sm1_1 = Components('sm1_1', bases=(sm1, ))
-    
+
     Now we create two utilities and insert them in our folder hierarchy:
-    
+
       >>> util1 = MyUtility('one', sm1)
       >>> sm1.registerUtility(util1, IMyUtility, 'myutil')
       >>> IComponentLookup(util1) is sm1
@@ -996,21 +998,21 @@
       >>> sm1_1.registerUtility(util1_1, IMyUtility, 'myutil')
       >>> IComponentLookup(util1_1) is sm1_1
       True
-    
+
     Now, if we ask `util1_1` for its next available utility we get the
     ``one`` utility::
-    
+
       >>> from zope.component import getNextUtility
       >>> getNextUtility(util1_1, IMyUtility, 'myutil')
       MyUtility('one')
-    
+
     Next we ask `util1` for its next utility and we should get the global version:
-    
+
       >>> getNextUtility(util1, IMyUtility, 'myutil')
       MyUtility('global')
-    
+
     However, if we ask the global utility for the next one, an error is raised
-    
+
       >>> getNextUtility(gutil, IMyUtility,
       ...                     'myutil') #doctest: +NORMALIZE_WHITESPACE
       Traceback (most recent call last):
@@ -1018,35 +1020,68 @@
       ComponentLookupError:
       No more utilities for <InterfaceClass zope.component.tests.IMyUtility>,
       'myutil' have been found.
-    
+
     You can also use `queryNextUtility` and specify a default:
-    
+
       >>> from zope.component import queryNextUtility
       >>> queryNextUtility(gutil, IMyUtility, 'myutil', 'default')
       'default'
-    
+
     Let's now ensure that the function also works with multiple registries. First
     we create another base registry:
-    
+
       >>> myregistry = Components()
-    
+
     We now set up another utility into that registry:
-    
+
       >>> custom_util = MyUtility('my_custom_util', myregistry)
       >>> myregistry.registerUtility(custom_util, IMyUtility, 'my_custom_util')
-    
+
     We add it as a base to the local site manager:
-    
+
       >>> sm1.__bases__ = (myregistry,) + sm1.__bases__
-    
+
     Both the ``myregistry`` and global utilities should be available:
-    
+
       >>> queryNextUtility(sm1, IMyUtility, 'my_custom_util')
       MyUtility('my_custom_util')
       >>> queryNextUtility(sm1, IMyUtility, 'myutil')
       MyUtility('global')
     """
 
+def dont_leak_utility_registrations_in__subscribers():
+    """
+
+    We've observed utilities getting left in _subscribers when they
+    get unregistered.
+
+    >>> import zope.component.registry
+    >>> reg = zope.component.registry.Components()
+    >>> class C:
+    ...     def __init__(self, name):
+    ...         self.name = name
+    ...     def __repr__(self):
+    ...         return "C(%s)" % self.name
+
+    >>> c1 = C(1)
+    >>> reg.registerUtility(c1, I1)
+    >>> reg.registerUtility(c1, I1)
+    >>> list(reg.getAllUtilitiesRegisteredFor(I1))
+    [C(1)]
+
+    >>> reg.unregisterUtility(provided=I1)
+    True
+    >>> list(reg.getAllUtilitiesRegisteredFor(I1))
+    []
+
+    >>> reg.registerUtility(c1, I1)
+    >>> reg.registerUtility(C(2), I1)
+
+    >>> list(reg.getAllUtilitiesRegisteredFor(I1))
+    [C(2)]
+
+    """
+
 class StandaloneTests(unittest.TestCase):
     def testStandalone(self):
         import subprocess



More information about the Checkins mailing list