[Zope3-checkins] SVN: Zope3/trunk/src/zope/ Fixed a bug in the
adapter-registration cache management for
Jim Fulton
jim at zope.com
Fri Apr 28 14:07:26 EDT 2006
Log message for revision 67729:
Fixed a bug in the adapter-registration cache management for
persistent registries. Unfortunately, the fix takes away about half of
my request-per-second performance win. :(
Changed:
U Zope3/trunk/src/zope/app/component/tests/test_registration.py
U Zope3/trunk/src/zope/component/persistentregistry.py
U Zope3/trunk/src/zope/component/tests.py
U Zope3/trunk/src/zope/interface/adapter.py
-=-
Modified: Zope3/trunk/src/zope/app/component/tests/test_registration.py
===================================================================
--- Zope3/trunk/src/zope/app/component/tests/test_registration.py 2006-04-28 17:57:47 UTC (rev 67728)
+++ Zope3/trunk/src/zope/app/component/tests/test_registration.py 2006-04-28 18:07:25 UTC (rev 67729)
@@ -27,12 +27,16 @@
import transaction
import persistent
+import zope.component.globalregistry
import zope.component.testing as placelesssetup
from zope.testing import doctest
from zope.app.testing import setup
import zope.app.container.contained
from zope import interface
+import zope.app.component.site
+
+
# test class for testing data conversion
class IFoo(interface.Interface):
pass
@@ -42,6 +46,9 @@
def __init__(self, name=''):
self.name = name
+ def __repr__(self):
+ return 'Foo(%r)' % self.name
+
def setUpOld(test):
placelesssetup.setUp(test)
setup.setUpAnnotations()
@@ -330,6 +337,75 @@
"""
+
+class GlobalRegistry:
+ pass
+
+base = zope.component.globalregistry.GlobalAdapterRegistry(
+ GlobalRegistry, 'adapters')
+GlobalRegistry.adapters = base
+def clear_base():
+ base.__init__(GlobalRegistry, 'adapters')
+
+
+def test_deghostification_of_persistent_adapter_registries():
+ """
+
+Note that this test duplicates one from zope.component.tests.
+We should be able to get rid of this one when we get rid of
+__setstate__ implementation we have in back35.
+
+We want to make sure that we see updates corrextly.
+
+ >>> import ZODB.tests.util
+ >>> db = ZODB.tests.util.DB()
+ >>> tm1 = transaction.TransactionManager()
+ >>> c1 = db.open(transaction_manager=tm1)
+ >>> r1 = zope.app.component.site._LocalAdapterRegistry((base,))
+ >>> r2 = zope.app.component.site._LocalAdapterRegistry((r1,))
+ >>> c1.root()[1] = r1
+ >>> c1.root()[2] = r2
+ >>> tm1.commit()
+ >>> r1._p_deactivate()
+ >>> r2._p_deactivate()
+
+ >>> tm2 = transaction.TransactionManager()
+ >>> c2 = db.open(transaction_manager=tm2)
+ >>> r1 = c2.root()[1]
+ >>> r2 = c2.root()[2]
+
+ >>> r1.lookup((), IFoo, '')
+
+ >>> base.register((), IFoo, '', Foo(''))
+ >>> r1.lookup((), IFoo, '')
+ Foo('')
+
+ >>> r2.lookup((), IFoo, '1')
+
+ >>> r1.register((), IFoo, '1', Foo('1'))
+
+ >>> r2.lookup((), IFoo, '1')
+ Foo('1')
+
+ >>> r1.lookup((), IFoo, '2')
+ >>> r2.lookup((), IFoo, '2')
+
+ >>> base.register((), IFoo, '2', Foo('2'))
+
+ >>> r1.lookup((), IFoo, '2')
+ Foo('2')
+
+ >>> r2.lookup((), IFoo, '2')
+ Foo('2')
+
+Cleanup:
+
+ >>> db.close()
+ >>> clear_base()
+
+ """
+
+
def test_suite():
suite = unittest.TestSuite((
doctest.DocFileSuite('deprecated35_statusproperty.txt',
Modified: Zope3/trunk/src/zope/component/persistentregistry.py
===================================================================
--- Zope3/trunk/src/zope/component/persistentregistry.py 2006-04-28 17:57:47 UTC (rev 67728)
+++ Zope3/trunk/src/zope/component/persistentregistry.py 2006-04-28 18:07:25 UTC (rev 67729)
@@ -21,14 +21,28 @@
import zope.component.registry
-class PersistentAdapterRegistry(zope.interface.adapter.AdapterRegistry,
- persistent.Persistent):
+class PersistentAdapterRegistry(
+ zope.interface.adapter.VerifyingAdapterRegistry,
+ persistent.Persistent,
+ ):
def changed(self, originally_changed):
if originally_changed is self:
self._p_changed = True
super(PersistentAdapterRegistry, self).changed(originally_changed)
+
+ def __getstate__(self):
+ state = super(PersistentAdapterRegistry, self).__getstate__().copy()
+ for name in self._delegated:
+ state.pop(name, 0)
+ return state
+
+ def __setstate__(self, state):
+ super(PersistentAdapterRegistry, self).__setstate__(state)
+ self._createLookup()
+ self._v_lookup.changed(self)
+
class PersistentComponents(zope.component.registry.Components):
def _init_registries(self):
Modified: Zope3/trunk/src/zope/component/tests.py
===================================================================
--- Zope3/trunk/src/zope/component/tests.py 2006-04-28 17:57:47 UTC (rev 67728)
+++ Zope3/trunk/src/zope/component/tests.py 2006-04-28 18:07:25 UTC (rev 67729)
@@ -17,6 +17,9 @@
"""
import re
import unittest
+import transaction
+import persistent
+
from zope import interface, component
from zope.interface.verify import verifyObject
from zope.interface.interfaces import IInterface
@@ -26,6 +29,8 @@
from zope.component.interfaces import IComponentArchitecture
from zope.component.interfaces import IComponentLookup
from zope.component.testing import setUp, tearDown
+import zope.component.persistentregistry
+import zope.component.globalregistry
# side effect gets component-based event dispatcher installed.
# we should obviously make this more explicit
@@ -837,6 +842,89 @@
"""
+
+
+class GlobalRegistry:
+ pass
+
+base = zope.component.globalregistry.GlobalAdapterRegistry(
+ GlobalRegistry, 'adapters')
+GlobalRegistry.adapters = base
+def clear_base():
+ base.__init__(GlobalRegistry, 'adapters')
+
+class IFoo(interface.Interface):
+ pass
+class Foo(persistent.Persistent):
+ interface.implements(IFoo)
+ name = ''
+ def __init__(self, name=''):
+ self.name = name
+
+ def __repr__(self):
+ return 'Foo(%r)' % self.name
+
+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()
+ >>> c1 = db.open(transaction_manager=tm1)
+ >>> r1 = zope.component.persistentregistry.PersistentAdapterRegistry(
+ ... (base,))
+ >>> r2 = zope.component.persistentregistry.PersistentAdapterRegistry((r1,))
+ >>> c1.root()[1] = r1
+ >>> c1.root()[2] = r2
+ >>> tm1.commit()
+ >>> r1._p_deactivate()
+
+ >>> len(base._v_subregistries)
+ 0
+
+ >>> tm2 = transaction.TransactionManager()
+ >>> c2 = db.open(transaction_manager=tm2)
+ >>> r1 = c2.root()[1]
+ >>> r2 = c2.root()[2]
+
+ >>> r1.lookup((), IFoo, '')
+
+ >>> base.register((), IFoo, '', Foo(''))
+ >>> r1.lookup((), IFoo, '')
+ Foo('')
+
+ >>> r2.lookup((), IFoo, '1')
+
+ >>> r1.register((), IFoo, '1', Foo('1'))
+
+ >>> r2.lookup((), IFoo, '1')
+ Foo('1')
+
+ >>> r1.lookup((), IFoo, '2')
+ >>> r2.lookup((), IFoo, '2')
+
+ >>> base.register((), IFoo, '2', Foo('2'))
+
+ >>> r1.lookup((), IFoo, '2')
+ Foo('2')
+
+ >>> r2.lookup((), IFoo, '2')
+ Foo('2')
+
+Cleanup:
+
+ >>> db.close()
+ >>> clear_base()
+
+ """
+
+
+
def tearDownRegistryTests(tests):
import zope.event
zope.event.subscribers.pop()
Modified: Zope3/trunk/src/zope/interface/adapter.py
===================================================================
--- Zope3/trunk/src/zope/interface/adapter.py 2006-04-28 17:57:47 UTC (rev 67728)
+++ Zope3/trunk/src/zope/interface/adapter.py 2006-04-28 18:07:25 UTC (rev 67729)
@@ -19,105 +19,72 @@
import weakref
from zope.interface import providedBy, Interface, ro
-class readproperty(object):
+_marker = object
+class BaseAdapterRegistry(object):
- def __init__(self, func):
- self.func = func
+ # List of methods copied from lookup sub-objects:
+ _delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter',
+ 'adapter_hook', 'lookupAll', 'names',
+ 'subscriptions', 'subscribers')
- def __get__(self, inst, class_):
- if inst is None:
- return self
+ # All registries maintain a generation that can be used by verifying
+ # registries
+ _generation = 0
- func = self.func
- return func(inst)
-
+ def __init__(self, bases=()):
-_delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter',
- 'adapter_hook', 'lookupAll', 'names',
- 'subscriptions', 'subscribers')
+ # {order -> {required -> {provided -> {name -> value}}}}
+ # where "interfaces" is really a nested key. So, for example:
+ # for order == 0, we have:
+ # {provided -> {name -> valie}}
+ # but for order == 2, we have:
+ # {r1 -> {r2 -> {provided -> {name -> valie}}}}
+ self._adapters = []
-_marker = object
-class AdapterRegistry(object):
+ # {order -> {required -> {provided -> {name -> [value]}}}}
+ # where the remarks about adapters above apply
+ self._subscribers = []
- def __init__(self, bases=()):
- self._adapters = []
- self._subscribers = []
+ # Set, with a reference count, keeping track of the interfaces
+ # for which we have provided components:
self._provided = {}
- self._init_non_persistent()
- self.__bases__ = bases
- def _init_non_persistent(self):
- self._v_subregistries = weakref.WeakKeyDictionary()
- self._v_lookup = lookup = AdapterLookup(self)
- for name in _delegated:
- self.__dict__[name] = getattr(lookup, name)
+ # Looup object to perform lookup. We make this a separate object to
+ # to make it easier, in the furture, to implement just the lookup
+ # functionality in C.
+ self._createLookup()
- def __getstate__(self):
- state = super(AdapterRegistry, self).__getstate__().copy()
- for name in _delegated:
- state.pop(name, 0)
- return state
+ # Cache invalidation data. There are really 2 kinds of registries:
+
+ # Invalidating registries have caches that are invalidated
+ # when they or when base registies change. An invalidating
+ # registry can only have invalidating registries as bases.
- def __setstate__(self, state):
- super(AdapterRegistry, self).__setstate__(state)
- self._init_non_persistent()
+ # Verifying registies can't rely on getting invalidation message,
+ # so have to check the generations of base registries to determine
+ # if their cache data are current
- @apply
- def __bases__():
+ # Base registries:
+ self.__bases__ = bases
- def get(self):
- return self.__dict__['__bases__']
+ def _setBases(self, bases):
+ self.__dict__['__bases__'] = bases
+ self.ro = ro.ro(self)
+ self.changed(self)
- def set(self, v):
- old = self.__dict__.get('__bases__', ())
- for r in old:
- if r not in v:
- r._removeSubregistry(self)
- for r in v:
- if r not in old:
- r._addSubregistry(self)
-
- self.__dict__['__bases__'] = v
- self.ro = ro.ro(self)
- self.changed(self)
-
- return property(get, set)
+ __bases__ = property(lambda self: self.__dict__['__bases__'],
+ lambda self, bases: self._setBases(bases),
+ )
- def _addSubregistry(self, r):
- self._v_subregistries[r] = 1
+ def _createLookup(self):
+ self._v_lookup = self.LookupClass(self)
+ for name in self._delegated:
+ self.__dict__[name] = getattr(self._v_lookup, name)
- def _removeSubregistry(self, r):
- if r in self._v_subregistries:
- del self._v_subregistries[r]
-
def changed(self, originally_changed):
- try:
- lookup = self._v_lookup
- except AttributeError:
- pass
- else:
- lookup.changed(originally_changed)
+ self._generation += 1
+ self._v_lookup.changed(originally_changed)
- for sub in self._v_subregistries.keys():
- sub.changed(originally_changed)
-
- @readproperty
- def _v_extendors(self):
- _v_extendors = {}
- for provided in self._provided:
- for i in provided.__iro__:
- extendors = _v_extendors.get(i, ())
- if provided not in extendors:
- _v_extendors[i] = (
- [e for e in extendors if provided.isOrExtends(e)]
- +
- [provided]
- +
- [e for e in extendors if not provided.isOrExtends(e)]
- )
- self._v_extendors = _v_extendors
- return self._v_extendors
-
def register(self, required, provided, name, value):
if value is None:
self.unregister(required, provided, name, value)
@@ -146,8 +113,8 @@
n = self._provided.get(provided, 0) + 1
self._provided[provided] = n
- if n == 1 and '_v_extendors' in self.__dict__:
- del self.__dict__['_v_extendors']
+ if n == 1:
+ self._v_lookup.add_extendor(provided)
self.changed(self)
@@ -195,8 +162,7 @@
n = self._provided[provided] - 1
if n == 0:
del self._provided[provided]
- if '_v_extendors' in self.__dict__:
- del self.__dict__['_v_extendors']
+ self._v_lookup.remove_extendor(provided)
else:
self._provided[provided] = n
@@ -227,8 +193,8 @@
if provided is not None:
n = self._provided.get(provided, 0) + 1
self._provided[provided] = n
- if n == 1 and '_v_extendors' in self.__dict__:
- del self.__dict__['_v_extendors']
+ if n == 1:
+ self._v_lookup.add_extendor(provided)
self.changed(self)
@@ -265,8 +231,7 @@
n = self._provided[provided] + len(new) - len(old)
if n == 0:
del self._provided[provided]
- if '_v_extendors' in self.__dict__:
- del self.__dict__['_v_extendors']
+ self._v_lookup.remove_extendor(provided)
self.changed(self)
@@ -278,8 +243,6 @@
return XXXTwistedFakeOut
-
-
_not_in_mapping = object()
class AdapterLookup(object):
@@ -289,8 +252,9 @@
self._mcache = {}
self._scache = {}
self._required = {}
+ self.init_extendors()
- def changed(self, originally_changed):
+ def changed(self, ignored=None):
self._cache.clear()
self._mcache.clear()
self._scache.clear()
@@ -299,6 +263,53 @@
if r is not None:
r.unsubscribe(self)
self._required.clear()
+
+
+ # Extendors
+ # ---------
+
+ # When given an target interface for an adapter lookup, we need to consider
+ # adapters for interfaces that extend the target interface. This is
+ # what the extendors dictionary is about. It tells us all of the
+ # interfaces that extend an interface for which there are adapters
+ # registered.
+
+ # We could separate this by order and name, thus reducing the
+ # number of provided interfaces to search at run time. The tradeoff,
+ # however, is that we have to store more information. For example,
+ # is the same interface is provided for multiple names and if the
+ # interface extends many interfaces, we'll have to keep track of
+ # a fair bit of information for each name. It's better to
+ # be space efficient here and be time efficient in the cache
+ # implementation.
+
+ # TODO: add invalidation when a provided interface changes, in case
+ # the interface's __iro__ has changed. This is unlikely enough that
+ # we'll take our chances for now.
+
+ def init_extendors(self):
+ self._extendors = {}
+ for p in self._registry._provided:
+ self.add_extendor(p)
+
+ def add_extendor(self, provided):
+ _extendors = self._extendors
+ for i in provided.__iro__:
+ extendors = _extendors.get(i, ())
+ _extendors[i] = (
+ [e for e in extendors if provided.isOrExtends(e)]
+ +
+ [provided]
+ +
+ [e for e in extendors if not provided.isOrExtends(e)]
+ )
+
+ def remove_extendor(self, provided):
+ _extendors = self._extendors
+ for i in provided.__iro__:
+ _extendors[i] = [e for e in _extendors.get(i, ())
+ if e != provided]
+
def _getcache(self, provided, name):
cache = self._cache.get(provided)
@@ -336,7 +347,7 @@
if order >= len(byorder):
continue
- extendors = registry._v_extendors.get(provided)
+ extendors = registry._v_lookup._extendors.get(provided)
if not extendors:
continue
@@ -412,7 +423,7 @@
byorder = registry._adapters
if order >= len(byorder):
continue
- extendors = registry._v_extendors.get(provided)
+ extendors = registry._v_lookup._extendors.get(provided)
if not extendors:
continue
components = byorder[order]
@@ -445,7 +456,7 @@
if provided is None:
extendors = (provided, )
else:
- extendors = registry._v_extendors.get(provided)
+ extendors = registry._v_lookup._extendors.get(provided)
if extendors is None:
continue
@@ -470,7 +481,84 @@
if subscriber is not None:
result.append(subscriber)
return result
+
+class AdapterRegistry(BaseAdapterRegistry):
+
+ LookupClass = AdapterLookup
+
+ def __init__(self, bases=()):
+ # AdapterRegisties are invalidating registries, so
+ # we need to keep track of out invalidating subregistries.
+ self._v_subregistries = weakref.WeakKeyDictionary()
+
+ super(AdapterRegistry, self).__init__(bases)
+
+ def _addSubregistry(self, r):
+ self._v_subregistries[r] = 1
+
+ def _removeSubregistry(self, r):
+ if r in self._v_subregistries:
+ del self._v_subregistries[r]
+
+ def _setBases(self, bases):
+ old = self.__dict__.get('__bases__', ())
+ for r in old:
+ if r not in bases:
+ r._removeSubregistry(self)
+ for r in bases:
+ if r not in old:
+ r._addSubregistry(self)
+
+ super(AdapterRegistry, self)._setBases(bases)
+
+ def changed(self, originally_changed):
+ super(AdapterRegistry, self).changed(originally_changed)
+
+ for sub in self._v_subregistries.keys():
+ sub.changed(originally_changed)
+
+
+class VerifyingAdapterLookup(AdapterLookup):
+
+ def __init__(self, registry):
+ super(VerifyingAdapterLookup, self).__init__(registry)
+
+ def changed(self, originally_changed):
+ super(VerifyingAdapterLookup, self).changed(originally_changed)
+ self._verify_ro = self._registry.ro[1:]
+ self._verify_generations = [r._generation for r in self._verify_ro]
+
+ def _verify(self):
+ if ([r._generation for r in self._verify_ro]
+ != self._verify_generations):
+ self.changed(None)
+
+ def _getcache(self, provided, name):
+ self._verify()
+
+ # The non-use of super here is intentional. A C implementation
+ # will call AdapterLookup's C implementation directly.
+ return AdapterLookup._getcache(self, provided, name)
+ def lookupAll(self, required, provided):
+ self._verify()
+
+ # The non-use of super here is intentional. A C implementation
+ # will call AdapterLookup's C implementation directly.
+ return AdapterLookup.lookupAll(self, required, provided)
+
+ def subscriptions(self, required, provided):
+ self._verify()
+
+ # The non-use of super here is intentional. A C implementation
+ # will call AdapterLookup's C implementation directly.
+ return AdapterLookup.subscriptions(self, required, provided)
+
+
+class VerifyingAdapterRegistry(BaseAdapterRegistry):
+
+ LookupClass = VerifyingAdapterLookup
+
def _convert_None_to_Interface(x):
if x is None:
return Interface
More information about the Zope3-Checkins
mailing list