Merge lp:~stub/launchpad/memcache into lp:launchpad

Proposed by Stuart Bishop
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 11044
Proposed branch: lp:~stub/launchpad/memcache
Merge into: lp:launchpad
Diff against target: 153 lines (+71/-21)
2 files modified
lib/lp/services/memcache/doc/tales-cache.txt (+32/-0)
lib/lp/services/memcache/tales.py (+39/-21)
To merge this branch: bzr merge lp:~stub/launchpad/memcache
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+28170@code.launchpad.net

Commit message

Allow the cache expiry in the cache: TALES expression to be dynamically calculated.

Description of the change

Address Bug #586461.

Allow the cache expiry in the cache: TALES expression to be dynamically calculated.

This work is needed to cache bug comments more aggressively.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/services/memcache/doc/tales-cache.txt'
--- lib/lp/services/memcache/doc/tales-cache.txt 2010-06-09 16:10:42 +0000
+++ lib/lp/services/memcache/doc/tales-cache.txt 2010-06-22 12:30:52 +0000
@@ -79,6 +79,38 @@
79 This bit cached up to 3 days.79 This bit cached up to 3 days.
8080
8181
82The unit defaults to 'seconds' if not specified.
83
84 >>> template = TestPageTemplate(dedent("""\
85 ... <body tal:omit-tag="">
86 ... <div tal:content="cache:public,30" tal:omit-tag="">
87 ... This bit cached up to 30 seconds.
88 ... </div>
89 ... </body>"""))
90 >>> print template()
91 This bit cached up to 30 seconds.
92
93
94A path expression can be used to dynamically calculate the cache time.
95
96 >>> template = TestPageTemplate(dedent("""\
97 ... <body tal:omit-tag="">
98 ... <div tal:content="cache:public,dynamic_time"
99 ... tal:omit-tag=""> This bit cached up to <span
100 ... tal:content="dynamic_time"
101 ... tal:omit-tag="">n</span> seconds.
102 ... </div>
103 ... <div tal:content="cache:public,dynamic_time hours"
104 ... tal:omit-tag=""> This bit cached up to <span
105 ... tal:content="dynamic_time"
106 ... tal:omit-tag="">n</span> hours.
107 ... </div>
108 ... </body>"""))
109 >>> print template(dynamic_time=30)
110 This bit cached up to 30 seconds.
111 This bit cached up to 30 hours.
112
113
82Visibility114Visibility
83----------115----------
84116
85117
=== modified file 'lib/lp/services/memcache/tales.py'
--- lib/lp/services/memcache/tales.py 2010-06-09 16:10:42 +0000
+++ lib/lp/services/memcache/tales.py 2010-06-22 12:30:52 +0000
@@ -43,6 +43,11 @@
43 </div>43 </div>
44 """44 """
45 implements(ITALESExpression)45 implements(ITALESExpression)
46
47 static_max_age = None # cache expiry if fixed
48 dynamic_max_age = None # callable if cache expiry is dynamic.
49 dynamic_max_age_unit = None # Multiplier for dynamic cache expiry result.
50
46 def __init__(self, name, expr, engine, traverser=simpleTraverse):51 def __init__(self, name, expr, engine, traverser=simpleTraverse):
47 """expr is in the format "visibility, 42 units".52 """expr is in the format "visibility, 42 units".
4853
@@ -101,28 +106,35 @@
101106
102 # Convert the max_age string to an integer number of seconds.107 # Convert the max_age string to an integer number of seconds.
103 if max_age is None:108 if max_age is None:
104 self.max_age = 0109 self.static_max_age = 0 # Never expire.
105 else:110 else:
111 # Extract the unit, if there is one. Unit defaults to seconds.
106 try:112 try:
107 value, unit = max_age.split(' ')113 value, unit = max_age.split(' ')
114 if unit[-1] == 's':
115 unit = unit[:-1]
116 if unit == 'second':
117 unit = 1
118 elif unit == 'minute':
119 unit = 60
120 elif unit == 'hour':
121 unit = 60 * 60
122 elif unit == 'day':
123 unit = 24 * 60 * 60
124 else:
125 raise SyntaxError(
126 "Unknown unit %s in cache: expression %s"
127 % (repr(unit), repr(expr)))
108 except ValueError:128 except ValueError:
109 raise SyntaxError(129 value = max_age
110 "Unparsable age %s in cache: expression"130 unit = 1
111 % repr(self.max_age))131
112 value = float(value)132 try:
113 if unit[-1] == 's':133 self.static_max_age = float(value) * unit
114 unit = unit[:-1]134 except (ValueError, TypeError):
115 if unit == 'second':135 self.dynamic_max_age = PathExpr(
116 pass136 name, value, engine, traverser)
117 elif unit == 'minute':137 self.dynamic_max_age_unit = unit
118 value *= 60
119 elif unit == 'hour':
120 value *= 60 * 60
121 elif unit == 'day':
122 value *= 24 * 60 * 60
123 else:
124 raise AssertionError("Unknown unit %s" % unit)
125 self.max_age = int(value)
126138
127 # For use with str.translate to sanitize keys. No control characters139 # For use with str.translate to sanitize keys. No control characters
128 # allowed, and we skip ':' too since it is a magic separator.140 # allowed, and we skip ':' too since it is a magic separator.
@@ -210,6 +222,11 @@
210222
211 return key223 return key
212224
225 def getMaxAge(self, econtext):
226 if self.dynamic_max_age is not None:
227 return self.dynamic_max_age(econtext) * self.dynamic_max_age_unit
228 return self.static_max_age
229
213 def __call__(self, econtext):230 def __call__(self, econtext):
214 # If we have an 'anonymous' visibility chunk and are logged in,231 # If we have an 'anonymous' visibility chunk and are logged in,
215 # we don't cache. Return the 'default' magic token to interpret232 # we don't cache. Return the 'default' magic token to interpret
@@ -225,7 +242,7 @@
225242
226 if cached_chunk is None:243 if cached_chunk is None:
227 logging.debug("Memcache miss for %s", key)244 logging.debug("Memcache miss for %s", key)
228 return MemcacheMiss(key, self)245 return MemcacheMiss(key, self.getMaxAge(econtext), self)
229 else:246 else:
230 logging.debug("Memcache hit for %s", key)247 logging.debug("Memcache hit for %s", key)
231 return MemcacheHit(cached_chunk)248 return MemcacheHit(cached_chunk)
@@ -244,8 +261,9 @@
244 tag contents and invokes this callback, which will store the261 tag contents and invokes this callback, which will store the
245 result in memcache against the key calculated by the MemcacheExpr.262 result in memcache against the key calculated by the MemcacheExpr.
246 """263 """
247 def __init__(self, key, memcache_expr):264 def __init__(self, key, max_age, memcache_expr):
248 self._key = key265 self._key = key
266 self._max_age = max_age
249 self._memcache_expr = memcache_expr267 self._memcache_expr = memcache_expr
250268
251 def __call__(self, value):269 def __call__(self, value):
@@ -255,7 +273,7 @@
255 value = "<!-- Cache hit: %s -->%s<!-- End cache hit: %s -->" % (273 value = "<!-- Cache hit: %s -->%s<!-- End cache hit: %s -->" % (
256 self._memcache_expr, value, self._memcache_expr)274 self._memcache_expr, value, self._memcache_expr)
257 if getUtility(IMemcacheClient).set(275 if getUtility(IMemcacheClient).set(
258 self._key, value, self._memcache_expr.max_age):276 self._key, value, self._max_age):
259 logging.debug("Memcache set succeeded for %s", self._key)277 logging.debug("Memcache set succeeded for %s", self._key)
260 else:278 else:
261 logging.warn("Memcache set failed for %s", self._key)279 logging.warn("Memcache set failed for %s", self._key)