Skip to content

Commit 3601ffc

Browse files
authored
Fix: validate that cached files on disk are fresh (#85)
If a logged-in user generated a file, LAST_TIME_RENDERED was set, but the cache on disk is not written. Now if there was an older version of the cache still on disk, on next request from a visitor that is not logged-in, the old cache would be served. Validate that the time set in LAST_TIME_RENDERED is in fact also the time of the cache-file on disk. If not, the file on disk is stale and a new page should be rendered.
1 parent 7101592 commit 3601ffc

2 files changed

Lines changed: 29 additions & 20 deletions

File tree

truewiki/metadata.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ def page():
4545
RELOAD_BUSY.set()
4646

4747

48+
def _delete_cached_page(page):
49+
if page in LAST_TIME_RENDERED:
50+
del LAST_TIME_RENDERED[page]
51+
52+
4853
def translation_callback(wtp, wiki_page, page):
4954
for wikilink in wtp.wikilinks:
5055
if wikilink.target.startswith("Translation:"):
@@ -56,8 +61,7 @@ def translation_callback(wtp, wiki_page, page):
5661
# Reset the last time rendered for all translations too, as
5762
# otherwise a new translation won't show up on those pages.
5863
for translation in TRANSLATIONS[target]:
59-
if translation in LAST_TIME_RENDERED:
60-
del LAST_TIME_RENDERED[translation]
64+
_delete_cached_page(translation)
6165

6266

6367
def category_callback(wtp, wiki_page, page):
@@ -69,8 +73,7 @@ def category_callback(wtp, wiki_page, page):
6973
CATEGORIES[target].append(page)
7074

7175
# Reset the last time rendered for the category.
72-
if f"Category/{target}" in LAST_TIME_RENDERED:
73-
del LAST_TIME_RENDERED[f"Category/{target}"]
76+
_delete_cached_page(f"Category/{target}")
7477

7578

7679
def file_callback(wtp, wiki_page, page):
@@ -82,8 +85,7 @@ def file_callback(wtp, wiki_page, page):
8285
FILES[target].append(page)
8386

8487
# Reset the last time rendered for the file.
85-
if f"File/{target}" in LAST_TIME_RENDERED:
86-
del LAST_TIME_RENDERED[f"File/{target}"]
88+
_delete_cached_page(f"File/{target}")
8789

8890

8991
def links_callback(wtp, wiki_page, page):
@@ -125,14 +127,10 @@ def template_callback(wtp, wiki_page, page):
125127
def _forget_page(page):
126128
for category in PAGES[page]["categories"]:
127129
CATEGORIES[category].remove(page)
128-
129-
if f"Category/{category}" in LAST_TIME_RENDERED:
130-
del LAST_TIME_RENDERED[f"Category/{category}"]
130+
_delete_cached_page(f"Category/{category}")
131131
for file in PAGES[page]["files"]:
132132
FILES[file].remove(page)
133-
134-
if f"File/{file}" in LAST_TIME_RENDERED:
135-
del LAST_TIME_RENDERED[f"File/{file}"]
133+
_delete_cached_page(f"File/{file}")
136134
for link in PAGES[page]["links"]:
137135
LINKS[link].remove(page)
138136
for template in PAGES[page]["templates"]:
@@ -144,9 +142,7 @@ def _forget_page(page):
144142
# otherwise a removed translation will still show up on those pages.
145143
if not translation.startswith(("Category/", "File/", "Template/")):
146144
translation = f"Page/{translation}"
147-
148-
if translation in LAST_TIME_RENDERED:
149-
del LAST_TIME_RENDERED[translation]
145+
_delete_cached_page(translation)
150146

151147
PAGES[page]["categories"].clear()
152148
PAGES[page]["files"].clear()
@@ -200,8 +196,7 @@ async def _page_changed(page, notified=None):
200196
# As we are invalidating this page, also reset when we last rendered it.
201197
# This means that on a next request for this page, browsers will be
202198
# given a new version too.
203-
if page in LAST_TIME_RENDERED:
204-
del LAST_TIME_RENDERED[page]
199+
_delete_cached_page(page)
205200

206201
# Capture the current templates ued. After analysis, this might have
207202
# changed, but those are still pages that need to be analyzed again.

truewiki/views/page.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ def view(user, page: str, if_modified_since) -> web.Response:
7373
# We already rendered this page before. If the browser has it in his
7474
# cache, he can simply reuse that if we haven't rendered since.
7575
response = web.HTTPNotModified()
76-
elif not user and cache_filename and os.path.exists(cache_filename):
76+
elif (
77+
not user
78+
and cache_filename
79+
and os.path.exists(cache_filename)
80+
and os.path.getmtime(cache_filename) >= metadata.LAST_TIME_RENDERED[namespaced_page]
81+
):
7782
# We already rendered this page to disk. Serve from there.
7883
with open(cache_filename) as fp:
7984
body = fp.read()
@@ -85,14 +90,23 @@ def view(user, page: str, if_modified_since) -> web.Response:
8590

8691
# Never cache anything in the Folder/.
8792
if can_cache:
88-
metadata.LAST_TIME_RENDERED[namespaced_page] = time.time()
89-
9093
if not user and cache_filename:
9194
# Cache the file on disk
9295
os.makedirs(os.path.dirname(cache_filename), exist_ok=True)
9396
with open(cache_filename, "w") as fp:
9497
fp.write(body)
9598

99+
page_time = os.path.getmtime(cache_filename)
100+
else:
101+
# Accuracy of time.time() is higher than getmtime(), so
102+
# depending if we cached, use a different clock.
103+
page_time = time.time()
104+
105+
# Only update the time if we don't have one yet. This makes sure
106+
# that LAST_TIME_RENDERED has the oldest timestamp possible.
107+
if namespaced_page not in metadata.LAST_TIME_RENDERED:
108+
metadata.LAST_TIME_RENDERED[namespaced_page] = page_time
109+
96110
response = web.Response(body=body, content_type="text/html", status=status_code)
97111

98112
# Inform the browser under which rules it can cache this page.

0 commit comments

Comments
 (0)