Files
neuf-www/web/src/instrumentation.ts
T

101 lines
3.1 KiB
TypeScript

import { osloDay } from "@/lib/date";
import {
purgeOwnCache,
setInternalRevalidateSecret,
} from "@/lib/internalRevalidate";
import {
CMS_CACHE_TAG,
TODAY_CACHE_TAG,
secondsUntilOsloMidnight,
} from "@/lib/revalidation";
// How often the clock is checked. Timers alone can't be trusted to fire at
// midnight: they stand still while a host or VM is paused
const CHECK_INTERVAL_MS = 30_000;
// Until the first purge has gone through. The server is already listening
// when register() runs, so this is only for a purge that fails
const STARTUP_RETRY_MS = 1_000;
function keepCacheCurrent() {
// the Oslo day the cache was last purged for; undefined until the startup
// purge has gone through
let purgedDay: string | undefined;
let repeatTodayPurge = false;
let failures = 0;
async function check() {
try {
await purgeWhatIsDue();
} catch (error) {
// nothing in there is expected to throw, but if something does the
// loop must survive it: without it the cache never rolls over
console.error("Checking the page cache failed", error);
} finally {
schedule();
}
}
async function purgeWhatIsDue() {
const day = osloDay();
let error: string | null = null;
if (purgedDay === undefined) {
// Pages prerendered by the build, or left on disk by an earlier run,
// may predate CMS edits and midnights this process never saw
error =
(await purgeOwnCache(CMS_CACHE_TAG)) ??
(await purgeOwnCache(TODAY_CACHE_TAG));
if (!error) purgedDay = day;
} else if (day !== purgedDay) {
error = await purgeOwnCache(TODAY_CACHE_TAG);
if (!error) {
purgedDay = day;
repeatTodayPurge = true;
}
} else if (repeatTodayPurge) {
// Next stamps a page when its render finishes, so a page that was
// being rendered across the purge looks newer than it and survives.
// A second purge one interval later catches those
error = await purgeOwnCache(TODAY_CACHE_TAG);
if (!error) repeatTodayPurge = false;
}
if (error) {
failures += 1;
// report the first failure once running, and then now and again; at
// startup only if it goes on for a minute
if (failures === 1 ? purgedDay !== undefined : failures % 60 === 0) {
console.error(`Purging the page cache keeps failing: ${error}`);
}
} else {
failures = 0;
}
}
function schedule() {
const delay =
purgedDay === undefined
? STARTUP_RETRY_MS
: // one second past midnight, so the purge can't land on the old day
Math.min(CHECK_INTERVAL_MS, (secondsUntilOsloMidnight() + 1) * 1000);
setTimeout(check, delay).unref();
}
void check();
}
export function register() {
// only in the running production server: not during `next build`, in
// `next dev` (nothing is kept between requests there) or on the edge
if (
process.env.NEXT_RUNTIME !== "nodejs" ||
process.env.NODE_ENV !== "production" ||
process.env.NEXT_PHASE === "phase-production-build"
) {
return;
}
setInternalRevalidateSecret(crypto.randomUUID());
keepCacheCurrent();
}