[CalendarServer-changes] [2361] CalendarServer/branches/propfind-cache/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Fri May 2 10:19:35 PDT 2008


Revision: 2361
          http://trac.macosforge.org/projects/calendarserver/changeset/2361
Author:   dreid at apple.com
Date:     2008-05-02 10:19:31 -0700 (Fri, 02 May 2008)

Log Message:
-----------
Drastically simplified Cache implementation, need to refactor notifier implementation so there is only one token.

Modified Paths:
--------------
    CalendarServer/branches/propfind-cache/twistedcaldav/cache.py
    CalendarServer/branches/propfind-cache/twistedcaldav/test/test_cache.py

Modified: CalendarServer/branches/propfind-cache/twistedcaldav/cache.py
===================================================================
--- CalendarServer/branches/propfind-cache/twistedcaldav/cache.py	2008-05-01 18:04:50 UTC (rev 2360)
+++ CalendarServer/branches/propfind-cache/twistedcaldav/cache.py	2008-05-02 17:19:31 UTC (rev 2361)
@@ -18,8 +18,12 @@
 import time
 import os
 
+from zope.interface import implements
+
 from twisted.python.filepath import FilePath
+from twisted.python import log
 
+from twisted.web2.iweb import IResource
 from twisted.web2.dav import davxml
 from twisted.web2.http import HTTPError
 from twisted.web2.dav.xattrprops import xattrPropertyStore
@@ -80,100 +84,99 @@
 
 
 
-class CacheChangeObserver(CacheChangeLoaderMixin):
-    def __init__(self, propertyStore):
-        self._propertyStore = propertyStore
-        self._oldPropToken = None
-        self._oldDataToken = None
+class ResponseCache(object):
+    """
+    An object that caches responses to given requests.
 
+    @ivar CACHE_TIMEOUT: The number of seconds that a cache entry is valid,
+        (default 3600 seconds or 1 hour).
 
-    def propertiesHaveChanged(self):
-        self._loadTokens()
+    @ivar _docroot: An L{FilePath} that points to the document root.
+    @ivar _responses: A C{dict} with (request-method, request-uri,
+         principal-uri) keys and (principal-token, uri-token, cache-time,
+         response) values.
+    """
 
-        if self._propToken != self._oldPropToken:
-            self._oldPropToken = self._propToken
-            return True
+    CACHE_TIMEOUT = 60*60 #1hr
 
-        return False
+    def __init__(self, docroot):
+        self._docroot = docroot
+        self._responses = {}
 
 
-    def dataHasChanged(self):
-        self._loadTokens()
+    def _tokenForURI(self, uri):
+        """
+        Get a property store for the given C{uri}.
 
-        if self._dataToken != self._oldDataToken:
-            self._oldDataToken = self._dataToken
-            return True
+        @param uri: The URI we'd like the token for.
+        @return: A C{str} representing the token for the URI.
+        """
 
-        return False
+        class __FauxStaticResource(object):
+            def __init__(self, fp):
+                self.fp = fp
 
 
+        fp = self._docroot
+        for childPath in uri.split('/')[:4]:
+            fp = fp.child(childPath)
 
-class PropfindCachingResource(object):
-    CACHE_TIMEOUT = 60*60 # 1 hour
+        props = self.propertyStoreFactory(__FauxStaticResource(fp))
 
-    propertyStoreFactory = xattrPropertyStore
-    observerFactory = CacheChangeObserver
+        try:
+            tokenElement = props.get(CacheTokensProperty.qname())
+            return tokenElement.children[0].data
 
-    def __init__(self, docroot, timeFunc=time.time):
-        self._docroot = docroot
-        self._responses = {}
-        self._observers = {}
-        self._timeFunc = timeFunc
+        except HTTPError, err:
+            pass
 
 
-    def _tokenPathForURI(self, uri):
-        tokenPath = self._docroot
+    def _time(self):
+        """
+        Return the current time in seconds since the epoch
+        """
+        return time.time()
 
-        for childName in uri.split('/')[:4]:
-            tokenPath = tokenPath.child(childName)
 
-        return tokenPath
+    def getResponseForRequest(self, request):
+        """
+        Retrieve a cached response to the given C{request} otherwise return
+        C{None}
 
+        @param request: An L{IRequest} provider that will be used to locate
+            a cached L{IResponse}.
 
-    def _observerForURI(self, uri):
-        class FauxStaticResource(object):
-            def __init__(self, fp):
-                self.fp = fp
+        @return: An L{IResponse} or C{None} if the response has not been cached.
+        """
+        key = (request.method, request.uri, request.authnUser)
+        if key not in self._responses:
+            return None
 
-        propertyStore = self.propertyStoreFactory(
-                FauxStaticResource(self._tokenPathForURI(uri)))
+        principalToken, uriToken, cacheTime, response = self._responses[key]
 
-        return self.observerFactory(propertyStore)
+        if self._tokenForURI(request.authnUser) != principalToken:
+            return None
 
+        elif self._tokenForURI(request.uri) != uriToken:
+            return None
 
-    def _cacheResponse(self, request, response):
-        if getattr(request, 'cacheRequest', False):
-            if request.uri not in self._observers:
-                self._observers[request.uri] = self._observerForURI(request.uri)
+        elif self._time() >= cacheTime + self.CACHE_TIMEOUT:
+            return None
 
-            self._responses[(request.method,
-                             request.uri,
-                             request.authnUser)] = (self._timeFunc(),
-                                                    response)
-
         return response
 
 
-    def renderHTTP(self, request):
-        key = (request.method, request.uri, request.authnUser)
+    def cacheResponseForRequest(self, request, response):
+        """
+        Cache the given C{response} for the given C{request}.
 
-        if key in self._responses:
-            cacheTime, cachedResponse = self._responses[key]
-            if cacheTime + CACHE_TIMEOUT <= self._timeFunc():
-                if (request.uri in self._observers and
-                    self._observers[request.uri]()):
+        @param request: An L{IRequest} provider that will be keyed to the
+            given C{response}.
 
-                    return cachedResponse
-
-        def _renderResource(resource, request):
-            request.addResponseFilter(self._cacheResponse)
-            return resource.renderHTTP(request)
-
-        request.notInCache = True
-        d = request.locateResource(request.uri)
-        d.addCallback(_renderResource, request)
-        return d
-
-
-    def locateChild(self, request, segments):
-        return self, []
+        @param response: An L{IResponse} provider that will be returned on
+            subsequent checks for the given L{IRequest}
+        """
+        key = (request.method, request.uri, request.authnUser)
+        self._responses[key] = (self._tokenForURI(request.authnUser),
+                                self._tokenForURI(request.uri),
+                                self._time(), response)

Modified: CalendarServer/branches/propfind-cache/twistedcaldav/test/test_cache.py
===================================================================
--- CalendarServer/branches/propfind-cache/twistedcaldav/test/test_cache.py	2008-05-01 18:04:50 UTC (rev 2360)
+++ CalendarServer/branches/propfind-cache/twistedcaldav/test/test_cache.py	2008-05-02 17:19:31 UTC (rev 2361)
@@ -22,8 +22,7 @@
 
 from twistedcaldav.cache import CacheChangeNotifier
 from twistedcaldav.cache import CacheTokensProperty
-from twistedcaldav.cache import CacheChangeObserver
-from twistedcaldav.cache import PropfindCachingResource
+from twistedcaldav.cache import ResponseCache
 
 from twistedcaldav.test.util import InMemoryPropertyStore
 
@@ -40,22 +39,6 @@
 
 
 
-class StubCacheChangeObserver(CacheChangeObserver):
-    def __init__(self, ps):
-        self._ps = ps
-        self.fp = self._ps.resource.fp
-
-        self._oldPropToken = None
-        self._oldDataToken = None
-        self.curPropToken = None
-        self.curDataToken = None
-
-    def _loadTokens(self):
-        self._dataToken = self.curDataToken
-        self._propToken = self.curPropToken
-
-
-
 class StubRequest(object):
     def __init__(self, method, uri, authnUser):
         self.method = method
@@ -108,139 +91,108 @@
 
 
 
-class CacheChangeObserverTests(TestCase):
+class ResponseCacheTests(TestCase):
     def setUp(self):
-        self.props = InMemoryPropertyStore()
-        self.props._properties[CacheTokensProperty.qname()
-                               ] = CacheTokensProperty.fromString(
-            'propToken0:dataToken0')
-        self.observer = CacheChangeObserver(self.props)
+        self.tokens = {
+                '/calendars/users/cdaboo/': 'uriToken0',
+                '/principals/users/cdaboo/': 'principalToken0',
+                '/principals/users/dreid/': 'principalTokenX'}
 
+        self.rc = ResponseCache(None)
+        self.rc._tokenForURI = self.tokens.get
 
-    def test_propertiesHaveChangedNewObserver(self):
-        self.assertEquals(self.observer.propertiesHaveChanged(), True)
+        self.rc._time = (lambda:0)
 
+        self.expected_response = object()
 
-    def test_propertiesHaveChanged(self):
-        self.assertEquals(self.observer.propertiesHaveChanged(), True)
+        self.rc._responses[(
+                'PROPFIND',
+                '/calendars/users/cdaboo/',
+                '/principals/users/cdaboo/')] = (
+            'principalToken0', 'uriToken0', 0, self.expected_response)
 
-        self.props._properties[CacheTokensProperty.qname()
-                               ] = CacheTokensProperty.fromString(
-            'propToken1:dataToken0')
 
-        self.assertEquals(self.observer.propertiesHaveChanged(), True)
+    def test_getResponseForRequestNotInCache(self):
+        response = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/users/dreid/',
+                '/principals/users/dreid/'))
 
+        self.assertEquals(response, None)
 
-    def test_propertiesHaveNotChanged(self):
-        self.assertEquals(self.observer.propertiesHaveChanged(), True)
-        self.assertEquals(self.observer.propertiesHaveChanged(), False)
 
+    def test_getResponseForRequestInCache(self):
+        response = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/users/cdaboo/',
+                '/principals/users/cdaboo/'))
 
-    def test_propertiesDoNotChangeData(self):
-        self.assertEquals(self.observer.propertiesHaveChanged(), True)
-        self.assertEquals(self.observer.dataHasChanged(), True)
+        self.assertEquals(self.expected_response, response)
 
-        self.props._properties[CacheTokensProperty.qname()
-                               ] = CacheTokensProperty.fromString(
-            'propToken1:dataToken0')
 
-        self.assertEquals(self.observer.propertiesHaveChanged(), True)
-        self.assertEquals(self.observer.dataHasChanged(), False)
+    def test_getResponseForRequestPrincipalTokenChanged(self):
+        self.tokens['/principals/users/cdaboo/'] = 'principalToken1'
 
+        response = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/users/cdaboo/',
+                '/principals/users/cdaboo/'))
 
-    def test_dataHasChanged(self):
-        self.assertEquals(self.observer.dataHasChanged(), True)
+        self.assertEquals(response, None)
 
-        self.props._properties[CacheTokensProperty.qname()
-                               ] = CacheTokensProperty.fromString(
-            'propToken0:dataToken1')
 
-        self.assertEquals(self.observer.dataHasChanged(), True)
+    def test_getResponseForRequestUriTokenChanged(self):
+        self.tokens['/calendars/users/cdaboo/'] = 'uriToken1'
 
+        response = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/users/cdaboo/',
+                '/principals/users/cdaboo/'))
 
-    def test_dataHasChangedNewObserver(self):
-        self.assertEquals(self.observer.dataHasChanged(), True)
+        self.assertEquals(response, None)
 
 
-    def test_dataHasNotChanged(self):
-        self.assertEquals(self.observer.dataHasChanged(), True)
-        self.assertEquals(self.observer.dataHasChanged(), False)
+    def test_getResponseForRequestCacheTimeoutLapsed(self):
+        self.rc._time = (lambda: 50000)
 
+        response = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/users/cdaboo/',
+                '/principals/users/cdaboo/'))
 
-    def test_dataDoesNotChangeProperties(self):
-        self.assertEquals(self.observer.dataHasChanged(), True)
-        self.assertEquals(self.observer.propertiesHaveChanged(), True)
+        self.assertEquals(response, None)
 
-        self.props._properties[CacheTokensProperty.qname()
-                               ] = CacheTokensProperty.fromString(
-            'propToken0:dataToken1')
 
-        self.assertEquals(self.observer.dataHasChanged(), True)
-        self.assertEquals(self.observer.propertiesHaveChanged(), False)
+    def test_cacheResponseForRequest(self):
+        expected_response = object()
+        self.rc.cacheResponseForRequest(StubRequest('PROPFIND',
+                                                    '/principals/users/dreid/',
+                                                    '/principals/users/dreid/'),
+                                        expected_response)
 
+        response = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/principals/users/dreid/',
+                '/principals/users/dreid/'))
 
+        self.assertEquals(response, expected_response)
 
-class PropfindCachingResourceTests(TestCase):
-    def setUp(self):
-        self.pcr = PropfindCachingResource(FilePath('/root'),
-                                                timeFunc=lambda:0)
 
-    def test_tokenPathForURI(self):
-        paths = [
-            ('/principals/__uids__/557C330A-06E2-403B-BC24-CE3A253CDB5B/',
-             '/root/principals/__uids__/557C330A-06E2-403B-BC24-CE3A253CDB5B'),
-            ('/calendars/users/dreid/', '/root/calendars/users/dreid'),
-            ('/calendars/users/dreid/calendar', '/root/calendars/users/dreid')]
+    def test__tokenForURI(self):
+        docroot = FilePath(self.mktemp())
+        principal = docroot.child('principals').child('users').child('wsanchez')
 
-        for inPath, outPath in paths:
-            self.assertEquals(self.pcr._tokenPathForURI(inPath).path, outPath)
+        expected_token = "wsanchezToken0"
 
+        props = InMemoryPropertyStore()
+        props._properties[CacheTokensProperty.qname()
+                          ] = CacheTokensProperty.fromString(expected_token)
 
-    def test_observerForURI(self):
-        self.pcr.observerFactory = StubCacheChangeObserver
+        stores = {principal.path: props}
 
-        paths = [
-            ('/principals/__uids__/557C330A-06E2-403B-BC24-CE3A253CDB5B/',
-             '/root/principals/__uids__/557C330A-06E2-403B-BC24-CE3A253CDB5B'),
-            ('/calendars/users/dreid/', '/root/calendars/users/dreid'),
-            ('/calendars/users/dreid/calendar', '/root/calendars/users/dreid')]
+        rc = ResponseCache(docroot)
 
-        for inPath, outPath in paths:
-            self.assertEquals(self.pcr._observerForURI(inPath).fp.path,
-                              outPath)
+        rc.propertyStoreFactory = (lambda rsrc: stores[rsrc.fp.path])
 
-
-    def test_cacheResponseTaggedRequestTrue(self):
-        response = object()
-        request = StubRequest('GET', '/root/calendars/users/dreid', 'dreid')
-
-        request.cacheRequest = True
-
-        r = self.pcr._cacheResponse(request, response)
-        self.assertEquals(r, response)
-
-        self.assertEquals(self.pcr._responses,
-                          {('GET',
-                            '/root/calendars/users/dreid',
-                            'dreid'): (0, response)})
-
-
-    def test_cacheResponseTaggedRequestFalse(self):
-        response = object()
-        request = StubRequest('GET', '/root/calendars/users/dreid', 'dreid')
-
-        r = self.pcr._cacheResponse(request, response)
-        self.assertEquals(r, response)
-
-        self.assertEquals(self.pcr._responses, {})
-
-
-    def test_cacheResposneUntaggedRequest(self):
-        response = object()
-        request = StubRequest('GET', '/root/calendars/users/dreid', 'dreid')
-        del request.cacheRequest
-
-        r = self.pcr._cacheResponse(request, response)
-        self.assertEquals(r, response)
-
-        self.assertEquals(self.pcr._responses, {})
+        token = rc._tokenForURI('/principals/users/wsanchez')
+        self.assertEquals(token, expected_token)

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080502/ad6b1008/attachment-0001.html


More information about the calendarserver-changes mailing list