-
Notifications
You must be signed in to change notification settings - Fork 3
/
rendcache.py
171 lines (152 loc) · 5.76 KB
/
rendcache.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#
# DWiki render cache manager
#
import htmlrends
# The validators.
# A new validator verifies nothing and always passes.
class Validator(object):
def __init__(self):
self.mtimes = {}
self.ctimes = {}
def add_ctime(self, page):
assert(page.exists())
self.ctimes[page.path] = page.modstamp
def add_mtime(self, page):
assert(page.exists())
self.mtimes[page.path] = page.timestamp
def verify(self, ctx):
for ppath, ts in self.mtimes.items():
np = ctx.model.get_page(ppath)
if np.timestamp != ts:
return False
for ppath, ts in self.ctimes.items():
np = ctx.model.get_page(ppath)
if np.modstamp != ts:
return False
return True
# Generate a general or a per-user key name from the general key name.
def keyname(ctx, kn, perUser):
if perUser and ctx.model.has_authentication() and ctx.login:
return kn + '-' + ctx.login
else:
return kn
def get_cstore(ctx):
return ctx[':_cachestore']
def cache_on(cfg):
return 'render-cache' in cfg
def gen_cache_on(cfg):
return cache_on(cfg) or 'generator-cache' in cfg
CACHENAME = 'renderers'
# Fetch and validate something from cname / sn / path / { key | key2 },
# with the given TTL if any. We try under key first then key2 if necessary
# and useful.
# key is usually general; key2 is per-user.
def _fetch(ctx, cname, sn, path, key, key2, TTL = None):
cstore = get_cstore(ctx)
res = cstore.fetch(cname, sn, path, key, TTL)
if not res and key2 != key:
res = cstore.fetch(cname, sn, path, key2, TTL)
if not res:
return None
(v, res) = res
if not v.verify(ctx):
return None
else:
return res
# Errors are reported through the context hook for doing so, if cache
# error warnings are enabled.
# Store data plus validator in cn / sn / path / key. Yes, The order
# of arguments is flipped. TODO: fix?
def _store(ctx, cn, sn, key, path, data, v):
cstore = get_cstore(ctx)
s = cstore.store((v, data), cn, sn, path, key)
if s and 'cache-warn-errors' in ctx:
ctx.set_error("renderer cache problem: %s" % s)
# Fetch the key NAME for ctx.page (on the current server hostname).
# fetch() has no TTL; the result is valid as long as the validator passes.
def fetch(ctx, name):
key = keyname(ctx, name, True)
key2 = keyname(ctx, name, False)
return _fetch(ctx, CACHENAME, ctx['server-name'], ctx.page.path,
key, key2)
# Fetch a heuristic cache item from 'generators' / 'all' / path / name.
# heuristic generators must be the same for all hosts and have a TTL
# after which they expire.
def fetch_gen(ctx, path, name, TTL = None):
if TTL is None:
# Global default heuristic TTL is one hour
TTL = ctx.cfg.get('render-heuristic-ttl', 60*60)
return _fetch(ctx, "generators", "all", path, name, name, TTL = TTL)
def store(ctx, name, data, v, perUser = False):
# If this is set, we only cache rendering done for the anonymous
# default user. Among other things, this avoids a proliferation
# of identical copies of the file for various different users.
# (Since most of the time, renderers are the same for people.)
if perUser and 'render-anonymous-only' in ctx.cfg and \
ctx.model.has_authentication() and \
(not ctx.is_login_default() or ctx.login is None):
return
key = keyname(ctx, name, perUser)
_store(ctx, CACHENAME, ctx['server-name'], key, ctx.page.path, data, v)
# Store data + v for a heuristic generator: 'generators' / 'all' / path / name
def store_gen(ctx, name, path, data, v):
_store(ctx, "generators", "all", name, path, data, v)
#
# Flagged heuristic generators are a special type of general heuristic
# generators. In addition to whatever validator is stored with the data,
# they are also invalidated by the presence of a flag quad in the cache
# store that is more recent than they are.
# The value stored in the flag quad is never used; the quad itself is
# stored only in order to update its file time. Flag quads always exist
# in the root of the cache store, so they globally invalidate everything
# when touched.
# Invalidate the given flagname. Flagnames are always stored as
# 'generators' / 'all' / '' / flagname
def invalidate_flagged(ctx, flagname):
cstore = get_cstore(ctx)
s = cstore.store("PUSH", "generators", "all", '', flagname)
if s and 'cache-warn-errors' in ctx:
ctx.set_error("renderer flag-store cache problem: %s" % s)
# Get a flagged heuristic generator. To be valid, it cannot be older
# than render-heuristic-flagged-ttl (default 48 hours) and it must
# be at least render-heuristic-flagged-delta (default 30 seconds)
# more recent than the flag quad if the latter exists.
# The delta exists to lessen races between cache generation and flag
# quad updates in two separate processes.
def get_flagged(ctx, name, path, flagname):
TTL = ctx.cfg.get('render-heuristic-flagged-ttl', 60*60*48)
delta = ctx.cfg.get('render-heuristic-flagged-delta', 30)
r = _fetch(ctx, "generators", "all", path, name, name, TTL = TTL)
if not r:
return r
# Validate timestamps.
cstore = get_cstore(ctx)
t1 = cstore.timeof('generators', 'all', path, name)
t2 = cstore.timeof('generators', 'all', '', flagname)
if not t2 or (t2+delta) < t1:
return r
else:
return None
#
# Generic infrastructure for simple caching of something that is
# just dependant on the page.
def simpleCacheWrap(func, name, perUser = False):
def _cachewrapped(ctx):
if not cache_on(ctx.cfg):
return func(ctx)
# The result is stored as a single-element tuple.
# This insures that if we have a result that is
# empty, we can tell it apart from a cache miss.
res = fetch(ctx, name)
if res:
return res[0]
# Miss; compute, store, return.
res = func(ctx)
v = Validator()
v.add_ctime(ctx.page)
store(ctx, name, (res,), v, perUser)
return res
_cachewrapped.__doc__ = func.__doc__
return _cachewrapped
def registerSimpleCached(name, func, perUser = False):
htmlrends.register(name, simpleCacheWrap(func, name, perUser))