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
1=== modified file 'lib/lp/services/memcache/doc/tales-cache.txt'
2--- lib/lp/services/memcache/doc/tales-cache.txt 2010-06-09 16:10:42 +0000
3+++ lib/lp/services/memcache/doc/tales-cache.txt 2010-06-22 12:30:52 +0000
4@@ -79,6 +79,38 @@
5 This bit cached up to 3 days.
6
7
8+The unit defaults to 'seconds' if not specified.
9+
10+ >>> template = TestPageTemplate(dedent("""\
11+ ... <body tal:omit-tag="">
12+ ... <div tal:content="cache:public,30" tal:omit-tag="">
13+ ... This bit cached up to 30 seconds.
14+ ... </div>
15+ ... </body>"""))
16+ >>> print template()
17+ This bit cached up to 30 seconds.
18+
19+
20+A path expression can be used to dynamically calculate the cache time.
21+
22+ >>> template = TestPageTemplate(dedent("""\
23+ ... <body tal:omit-tag="">
24+ ... <div tal:content="cache:public,dynamic_time"
25+ ... tal:omit-tag=""> This bit cached up to <span
26+ ... tal:content="dynamic_time"
27+ ... tal:omit-tag="">n</span> seconds.
28+ ... </div>
29+ ... <div tal:content="cache:public,dynamic_time hours"
30+ ... tal:omit-tag=""> This bit cached up to <span
31+ ... tal:content="dynamic_time"
32+ ... tal:omit-tag="">n</span> hours.
33+ ... </div>
34+ ... </body>"""))
35+ >>> print template(dynamic_time=30)
36+ This bit cached up to 30 seconds.
37+ This bit cached up to 30 hours.
38+
39+
40 Visibility
41 ----------
42
43
44=== modified file 'lib/lp/services/memcache/tales.py'
45--- lib/lp/services/memcache/tales.py 2010-06-09 16:10:42 +0000
46+++ lib/lp/services/memcache/tales.py 2010-06-22 12:30:52 +0000
47@@ -43,6 +43,11 @@
48 </div>
49 """
50 implements(ITALESExpression)
51+
52+ static_max_age = None # cache expiry if fixed
53+ dynamic_max_age = None # callable if cache expiry is dynamic.
54+ dynamic_max_age_unit = None # Multiplier for dynamic cache expiry result.
55+
56 def __init__(self, name, expr, engine, traverser=simpleTraverse):
57 """expr is in the format "visibility, 42 units".
58
59@@ -101,28 +106,35 @@
60
61 # Convert the max_age string to an integer number of seconds.
62 if max_age is None:
63- self.max_age = 0
64+ self.static_max_age = 0 # Never expire.
65 else:
66+ # Extract the unit, if there is one. Unit defaults to seconds.
67 try:
68 value, unit = max_age.split(' ')
69+ if unit[-1] == 's':
70+ unit = unit[:-1]
71+ if unit == 'second':
72+ unit = 1
73+ elif unit == 'minute':
74+ unit = 60
75+ elif unit == 'hour':
76+ unit = 60 * 60
77+ elif unit == 'day':
78+ unit = 24 * 60 * 60
79+ else:
80+ raise SyntaxError(
81+ "Unknown unit %s in cache: expression %s"
82+ % (repr(unit), repr(expr)))
83 except ValueError:
84- raise SyntaxError(
85- "Unparsable age %s in cache: expression"
86- % repr(self.max_age))
87- value = float(value)
88- if unit[-1] == 's':
89- unit = unit[:-1]
90- if unit == 'second':
91- pass
92- elif unit == 'minute':
93- value *= 60
94- elif unit == 'hour':
95- value *= 60 * 60
96- elif unit == 'day':
97- value *= 24 * 60 * 60
98- else:
99- raise AssertionError("Unknown unit %s" % unit)
100- self.max_age = int(value)
101+ value = max_age
102+ unit = 1
103+
104+ try:
105+ self.static_max_age = float(value) * unit
106+ except (ValueError, TypeError):
107+ self.dynamic_max_age = PathExpr(
108+ name, value, engine, traverser)
109+ self.dynamic_max_age_unit = unit
110
111 # For use with str.translate to sanitize keys. No control characters
112 # allowed, and we skip ':' too since it is a magic separator.
113@@ -210,6 +222,11 @@
114
115 return key
116
117+ def getMaxAge(self, econtext):
118+ if self.dynamic_max_age is not None:
119+ return self.dynamic_max_age(econtext) * self.dynamic_max_age_unit
120+ return self.static_max_age
121+
122 def __call__(self, econtext):
123 # If we have an 'anonymous' visibility chunk and are logged in,
124 # we don't cache. Return the 'default' magic token to interpret
125@@ -225,7 +242,7 @@
126
127 if cached_chunk is None:
128 logging.debug("Memcache miss for %s", key)
129- return MemcacheMiss(key, self)
130+ return MemcacheMiss(key, self.getMaxAge(econtext), self)
131 else:
132 logging.debug("Memcache hit for %s", key)
133 return MemcacheHit(cached_chunk)
134@@ -244,8 +261,9 @@
135 tag contents and invokes this callback, which will store the
136 result in memcache against the key calculated by the MemcacheExpr.
137 """
138- def __init__(self, key, memcache_expr):
139+ def __init__(self, key, max_age, memcache_expr):
140 self._key = key
141+ self._max_age = max_age
142 self._memcache_expr = memcache_expr
143
144 def __call__(self, value):
145@@ -255,7 +273,7 @@
146 value = "<!-- Cache hit: %s -->%s<!-- End cache hit: %s -->" % (
147 self._memcache_expr, value, self._memcache_expr)
148 if getUtility(IMemcacheClient).set(
149- self._key, value, self._memcache_expr.max_age):
150+ self._key, value, self._max_age):
151 logging.debug("Memcache set succeeded for %s", self._key)
152 else:
153 logging.warn("Memcache set failed for %s", self._key)