
Hello everyone, This is an updated version of the caching patch against current SVN. This is mostly code from the Nevow-caching branch from dialtone with the addition of a cacheTimeout logic on the whole rendering that I use for the whole non-user dependent dynamic pages of my site. I also use dialtone's stan cache for some fragment (not all unfortunately) and that was a significant speedup but I still need the other more aggressive caching in the Page class. Please let me know if you see any bug in this code, thanks! To me it looks useful enough to be merged in trunk but that's just my humble opinion. This code allows >200req per second to be served for dynamic but mostly static data. It doesn't max out a 100mbit yet with <5k pages, but it greatly exceeds the bandwidth of a 10mbit. Index: Nevow/nevow/tags.py =================================================================== --- Nevow/nevow/tags.py (revision 1185) +++ Nevow/nevow/tags.py (working copy) @@ -25,7 +25,7 @@ """ -from nevow.stan import Proto, Tag, directive, raw, xml, CommentProto, invisible, slot, cdata +from nevow.stan import Proto, Tag, directive, raw, xml, CommentProto, invisible, slot, cdata, cached comment = CommentProto() @@ -62,7 +62,9 @@ def inlineJS(s): return script(type="text/javascript", language="JavaScript")[xml('\n//<![CDATA[\n%s\n//]]>\n' % s)] -__all__ = tags + ['invisible', 'comment', '_dir', '_del', '_object', '_map', 'drange', 'Tag', 'directive', 'xml', 'raw', 'slot', 'cdata', 'inlineJS'] + ['_%s' % x for x in range(100)] +__all__ = tags + ['invisible', 'comment', '_dir', '_del', '_object', + '_map', 'drange', 'Tag', 'directive', 'xml', 'raw', + 'slot', 'cached', 'cdata', 'inlineJS'] + ['_%s' % x for x in range(100)] ######################## Index: Nevow/nevow/__init__.py =================================================================== --- Nevow/nevow/__init__.py (revision 1185) +++ Nevow/nevow/__init__.py (working copy) @@ -183,6 +183,7 @@ nevow.flat.flatstan.RendererSerializer nevow.inevow.IRenderer nevow.flat.flatstan.DirectiveSerializer nevow.stan.directive nevow.flat.flatstan.SlotSerializer nevow.stan.slot +nevow.flat.flatstan.CachedSerializer nevow.stan.cached nevow.flat.flatstan.ContextSerializer nevow.context.WovenContext nevow.flat.flatstan.DeferredSerializer twisted.internet.defer.Deferred nevow.flat.flatstan.DeferredSerializer twisted.internet.defer.DeferredList Index: Nevow/nevow/flat/flatstan.py =================================================================== --- Nevow/nevow/flat/flatstan.py (revision 1185) +++ Nevow/nevow/flat/flatstan.py (working copy) @@ -9,10 +9,14 @@ from nevow import util from nevow.stan import Proto, Tag, xml, directive, Unset, invisible from nevow.inevow import IRenderer, IRendererFactory, IGettable, IData -from nevow.flat import precompile, serialize +from nevow.flat import precompile, serialize, iterflatten from nevow.accessors import convertToData from nevow.context import WovenContext +from time import time as now +from cStringIO import StringIO +from twisted.internet import defer + allowSingleton = ('img', 'br', 'hr', 'base', 'meta', 'link', 'param', 'area', 'input', 'col', 'basefont', 'isindex', 'frame') @@ -226,6 +230,45 @@ return serialize(original.default, context) return serialize(data, context) +_CACHE = {} +def CachedSerializer(original, context): + cached = _CACHE.get(original.name, None) + _now = now() + life = _now-original.lifetime + if cached and (cached[0] > life or not original.lifetime): + yield cached[1] + return + io = StringIO() + for child in iterflatten(original.children, context, io.write, + lambda item: True): + if isinstance(child, tuple): + childDeferred, childReturner = child + + d = defer.Deferred() ## A new deferred for the outer loop, whose result + ## we don't care about, because we don't want the outer loop to write + ## anything when this deferred fires -- only when the entire for loop + ## has completed and we have all the "children" flattened + + def innerDeferredResultAvailable(result): + childReturner(result) ## Cause the inner iterflatten to continue + d.callback('') ## Cause the outer iterflatten to continue + return '' + + childDeferred.addCallback(innerDeferredResultAvailable) + + ## Make the outer loop wait on our new deferred. + ## We call the new deferred back with '' + ## Which will cause the outer loop to write '' to the request, + ## which doesn't matter. It will then call our "returner", + ## which is just the noop lambda below, because we don't care + ## about the return result of the new deferred, which is just + ## '' + + yield d, lambda result: '' + result = io.getvalue() + _CACHE[original.name] = (_now, result) + yield result + def ContextSerializer(original, context): originalContext = original.clone(deep=False) originalContext.precompile = context and context.precompile or False Index: Nevow/nevow/stan.py =================================================================== --- Nevow/nevow/stan.py (revision 1185) +++ Nevow/nevow/stan.py (working copy) @@ -119,8 +119,33 @@ """ raise NotImplementedError, "Stan slot instances are not iterable." +class cached(object): + """Marker for cached content + """ + __slots__ = ['name', 'children', 'lifetime'] + def __init__(self, name, lifetime=0): + self.name = name + self.children = [] + self.lifetime = lifetime + def __repr__(self): + return "cached('%s','%s')" % self.name, self.lifetime + + def __getitem__(self, children): + """cached content is what is being cached + """ + if not isinstance(children, (list, tuple)): + children = [children] + self.children.extend(children) + return self + + def __iter__(self): + """Prevent an infinite loop if someone tries to do + for x in cached('foo'): + """ + raise NotImplementedError, "Stan slot instances are not iterable." + class Tag(object): """Tag instances represent XML tags with a tag name, attributes, and children. Tag instances can be constructed using the Prototype Index: Nevow/nevow/rend.py =================================================================== --- Nevow/nevow/rend.py (revision 1185) +++ Nevow/nevow/rend.py (working copy) @@ -30,6 +30,7 @@ from nevow import flat from nevow.util import log from nevow import util +from nevow import url import formless from formless import iformless @@ -374,6 +375,7 @@ self.children = {} self.children[name] = child +_CACHE = {} class Page(Fragment, ConfigurableFactory, ChildLookupMixin): """A page is the main Nevow resource and renders a document loaded @@ -384,12 +386,48 @@ buffered = False + cacheTimeout = None # 0 means cache forever, >0 sets the seconds of caching + __lastCacheRendering = 0 # this should not be touched by the parent class + beforeRender = None afterRender = None addSlash = None flattenFactory = flat.flattenFactory + def hasCache(self, ctx): + if self.cacheTimeout is None: + return None + + _now = now() # run gettimeofday only once + timeout = _now > self.__lastCacheRendering + self.cacheTimeout and \ + self.cacheTimeout > 0 + c = self.lookupCache(ctx) + if timeout or c is None: + self.__lastCacheRendering = _now # stop other renders + from twisted.internet.defer import Deferred + d = Deferred() + self.storeCache(ctx, d) + # force only this rendering, others will wait the deferred + c = None + return c + def chainDeferredCache(self, ctx, d): + if self.cacheTimeout is None: + return d + + from twisted.internet.defer import Deferred + c = self.lookupCache(ctx) + if isinstance(c, Deferred): + # we're the thread that went ahead to refresh the cache + d.chainDeferred(c) + return d + def cacheIDX(self, ctx): + return str(url.URL.fromContext(ctx)) + def storeCache(self, ctx, c): + _CACHE[self.cacheIDX(ctx)] = c + def lookupCache(self, ctx): + return _CACHE.get(self.cacheIDX(ctx)) + def renderHTTP(self, ctx, doBefore=True): ## XXX request is really ctx now, change the name here request = inevow.IRequest(ctx) @@ -412,11 +450,17 @@ if self.afterRender is not None: return util.maybeDeferred(self.afterRender,ctx) - if self.buffered: + c = self.hasCache(ctx) + if c: + return util.maybeDeferred(finishRequest).addCallback(lambda r: c) + + if self.buffered or self.cacheTimeout is not None: io = StringIO() writer = io.write def finisher(result): - request.write(io.getvalue()) + c = io.getvalue() + self.storeCache(ctx, c) + request.write(c) return util.maybeDeferred(finishRequest).addCallback(lambda r: result) else: writer = request.write @@ -426,7 +470,7 @@ doc = self.docFactory.load() ctx = WovenContext(ctx, tags.invisible[doc]) - return self.flattenFactory(doc, ctx, writer, finisher) + return self.chainDeferredCache(ctx, self.flattenFactory(doc, ctx, writer, finisher)) def rememberStuff(self, ctx): Fragment.rememberStuff(self, ctx) This last bit avoids an error at runtime, clearly getStyleSheet should be overriden then (not stylesheet). Index: Nevow/nevow/vhost.py =================================================================== --- Nevow/nevow/vhost.py (revision 1185) +++ Nevow/nevow/vhost.py (working copy) @@ -19,7 +19,7 @@ """ def getStyleSheet(self): - return self.stylesheet + return VirtualHostList.stylesheet def data_hostlist(self, context, data): return self.nvh.hosts.keys()
participants (1)
-
Andrea Arcangeli