fix(ssr): Cache-Schreibfehler schlucken, App-Start absichern
Dieselbe Vorlage, dieselbe Auslieferung: `context.db.create("ssr", …)` ist
ein Insert, mehrere parallele Anfragen nach einer Invalidierung erzeugen
"duplicate key", und die Verlierer antworten mit 500 — ohne #appContainer,
also bleibt die Seite beim Besucher leer. Der Fehler wird geschluckt, die
Seite geht raus.
Der Block ist byte-gleich zu dem im event-website-tibi-2026-Projekt. Der
Starter ist die Vorlage; zwei Varianten desselben Fixes wären genau die
Drift, die hier nicht stattfinden darf.
Dazu der Einstiegspunkt, der denselben Fehlerklasse-Vorfall hat:
setupI18n().then(() => { hydrate(App, { target: appContainer }) })
Ein Fehler beim Laden der Locale-Datei oder ein werfendes hydrate()
lässt das ausgelieferte HTML stehen — ohne Signal, ohne Logeintrag, und für
einen Test nicht unterscheidbar von "noch nicht fertig". Jetzt steht der
Render in try/catch, der Fehler wird geloggt, und #appContainer bekommt
data-app-ready="error". Tests warten auf den Wert "true" und nennen bei
"error" die Ursache mit, statt still zu timeouten.
This commit is contained in:
@@ -194,14 +194,39 @@ const APP_SERVER_MODULE_PATH = "../lib/app.server"
|
||||
|
||||
if (cacheIt && !noCache) {
|
||||
// save cache
|
||||
context.db.create("ssr", {
|
||||
path: url,
|
||||
content: tpl,
|
||||
// @ts-ignore
|
||||
validUntil: context.ssrCacheValidUntil,
|
||||
// dependency strings: "col:id" for detail, "col:*" for list (deduplicated)
|
||||
dependencies: depsKeys,
|
||||
})
|
||||
//
|
||||
// `context.db.create` ist ein Insert, kein Upsert. Nach einer
|
||||
// Invalidierung rendern mehrere Anfragen dieselbe URL gleichzeitig,
|
||||
// und alle schreiben denselben Eintrag. Der Unique-Index auf `path`
|
||||
// lässt genau einen gewinnen; die Verlierer bekommen "duplicate
|
||||
// key".
|
||||
//
|
||||
// Das wird hier bewusst geschluckt. Der Eintrag existiert dann
|
||||
// schon — der Gewinner hat ihn geschrieben, es fehlt nichts. Und
|
||||
// die Seite ist ohnehin fertig gerendert, `tpl` steht bereit.
|
||||
//
|
||||
// Wichtig ist das aus zwei Gründen: Ein durchschlagender Fehler
|
||||
// liefert eine gute Seite als HTTP 500 aus, und ein
|
||||
// 500-Dokument hat kein #appContainer — die Seite bleibt beim
|
||||
// Besucher dann für immer leer, weil hydrate() nichts zum
|
||||
// Einhängen findet.
|
||||
//
|
||||
// Was hier nicht passiert: dass zweimal gerendert wird. Dafür bräuchte
|
||||
// es Koordination über Anfragen hinweg, und ein Hook hat keinen
|
||||
// geteilten Zustand. Das bleibt erstmal so — ein Ansturm kostet
|
||||
// Rechenzeit, keine Seite.
|
||||
try {
|
||||
context.db.create("ssr", {
|
||||
path: url,
|
||||
content: tpl,
|
||||
// @ts-ignore
|
||||
validUntil: context.ssrCacheValidUntil,
|
||||
// dependency strings: "col:id" for detail, "col:*" for list (deduplicated)
|
||||
dependencies: depsKeys,
|
||||
})
|
||||
} catch (e) {
|
||||
utils.log("ssr cache: Eintrag für " + url + " wurde parallel geschrieben, Anfrage ohne eigenen Cache-Eintrag")
|
||||
}
|
||||
}
|
||||
|
||||
throw {
|
||||
|
||||
Reference in New Issue
Block a user