Terminplaner: Minutenjob in Verfall und Mailversand getrennt geschuetzt
fuehreAus nimmt { teil } und sperrt je job:teil; minutenJob startet Verfall
und Mailversand (Warteschlange plus Betriebspruefung) gemeinsam mit
Promise.all. Haengt ein Teil, laeuft der andere im naechsten Takt weiter
(verfallUebersprungen bzw. mailUebersprungen); uebersprungen gilt nur, wenn
beide Teile uebersprungen werden. Die Ueberlappungswarnung nennt den Teil,
das Fehlerlog behaelt minutenJob.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aaed273b62
commit
e2f50407a0
2 files changed
+190
-40
No files matched your search
@@ -27,8 +27,10 @@ import { gibZellenFrei, raeumeVerwaisteZellen } from './zellen.js'
|
||||
* Hintergrund-Jobs des Terminplaners (Spezifikation 2026-10-02-terminplaner-design.md, Abschnitte 4 und 11). server/index.js
|
||||
* ruft sie mit new Date() auf; `now` ist hier immer ein Parameter.
|
||||
*
|
||||
* minutenJob jede Minute abgelaufene Haltungen => verfallen (Zellen frei); Mail-Warteschlange senden; zuletzt die
|
||||
* Betriebspruefung (Budget, Warteschlange, Anmeldepause => Log und Team-Mail, betrieb.js)
|
||||
* minutenJob jede Minute zwei Teile, gemeinsam gestartet (Promise.all) und je Teil gegen Ueberlappung geschuetzt:
|
||||
* Verfall: abgelaufene Haltungen => verfallen (Zellen frei);
|
||||
* Mailversand: Mail-Warteschlange senden, zuletzt die Betriebspruefung (Budget, Warteschlange,
|
||||
* Anmeldepause => Log und Team-Mail, betrieb.js)
|
||||
* fuenfMinutenJob alle 5 Minuten Erinnerungen (G05); bestaetigt 1 Stunde nach Ende => erledigt; Anfragen mit Beginn in
|
||||
* der Vergangenheit => abgelehnt (vom System, Zellen frei, keine Mail)
|
||||
* taeglicherJob taeglich Loeschfristen (11), festgefahrene und alte Mails, abgeschaltete Funktionen, verwaiste Zellen;
|
||||
@@ -38,7 +40,10 @@ import { gibZellenFrei, raeumeVerwaisteZellen } from './zellen.js'
|
||||
* Gemeinsame Regeln:
|
||||
* - Modus off: jeder Job kehrt sofort zurueck, ohne Abfrage, mit { aus: true }.
|
||||
* - Keine ueberlappenden Laeufe: laeuft ein Job noch, wird der naechste Takt uebersprungen ({ uebersprungen: true }, einmal im
|
||||
* Log je Ueberlappung).
|
||||
* Log je Ueberlappung). Die Sperre gilt je `job:teil` (fuehreAus mit { teil }): haengt beim Minutenjob der Verfall, versendet der
|
||||
* naechste Takt trotzdem die Warteschlange und umgekehrt; uebersprungen gilt der Takt erst, wenn beide Teile uebersprungen sind
|
||||
* (sonst steht verfallUebersprungen bzw. mailUebersprungen im Ergebnis). Die Warnung nennt den Teil (`minutenJob (Verfall)`), das
|
||||
* Fehlerlog behaelt `minutenJob` als Jobnamen.
|
||||
* - Ein Schritt eines Jobs scheitert nie den ganzen Job: der Fehler steht im Log (Text des Fehlers, bei einer Buchung mit
|
||||
* Buchungsnummer, nie mit Gastdaten), `fehler` im Ergebnis zaehlt ihn, die uebrigen Schritte laufen. Eine fehlende Collection
|
||||
* (404 collection_not_found, Einrichtung unvollstaendig) meldet ein Job hoechstens einmal je Stunde.
|
||||
@@ -100,23 +105,26 @@ async function schritt(lauf, was, fn, ersatz) {
|
||||
|
||||
/**
|
||||
* Rahmen jedes Jobs: Schalter, Ueberlappung, Laufdaten. => Ergebnis von arbeit(lauf) | { aus: true } | { uebersprungen: true }
|
||||
* `teil` (Name eines Teiljobs, z. B. „Verfall“) gibt dem Teil eine eigene Ueberlappungssperre `job:teil` und nennt ihn in der
|
||||
* Warnung (`job (teil)`); `lauf.job` bleibt der Jobname (Fehlerlog, Warnung bei fehlender Collection).
|
||||
*/
|
||||
async function fuehreAus(job, now, arbeit) {
|
||||
async function fuehreAus(job, now, arbeit, { teil = null } = {}) {
|
||||
const nowMs = pruefeNow(now, job)
|
||||
if (termineAus()) return { aus: true }
|
||||
if (laufend.has(job)) {
|
||||
if (!ueberlappGemeldet.has(job)) {
|
||||
ueberlappGemeldet.add(job)
|
||||
console.warn(`[termine-jobs] ${job}: der vorige Lauf ist noch nicht fertig, dieser Takt wird übersprungen`)
|
||||
const sperre = teil === null ? job : `${job}:${teil}`
|
||||
if (laufend.has(sperre)) {
|
||||
if (!ueberlappGemeldet.has(sperre)) {
|
||||
ueberlappGemeldet.add(sperre)
|
||||
console.warn(`[termine-jobs] ${teil === null ? job : `${job} (${teil})`}: der vorige Lauf ist noch nicht fertig, dieser Takt wird übersprungen`)
|
||||
}
|
||||
return { uebersprungen: true }
|
||||
}
|
||||
laufend.add(job)
|
||||
laufend.add(sperre)
|
||||
try {
|
||||
return await arbeit({ job, nowMs, fehler: 0, gedeckelt: 0 })
|
||||
} finally {
|
||||
laufend.delete(job)
|
||||
ueberlappGemeldet.delete(job)
|
||||
laufend.delete(sperre)
|
||||
ueberlappGemeldet.delete(sperre)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,19 +222,48 @@ async function verfalleHaltungen(lauf, now) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Jede Minute: abgelaufene Haltungen verfallen lassen, Warteschlange senden, zuletzt den Betrieb pruefen (Vorfaelle stehen im Log und
|
||||
* gehen als T20 ans Team; die Pruefung wirft nie und aendert das Ergebnis des Laufs nicht).
|
||||
* Jede Minute, in zwei unabhaengig geschuetzten Teilen, die gemeinsam starten (Promise.all):
|
||||
* - Verfall: abgelaufene Haltungen verfallen lassen;
|
||||
* - Mailversand: Warteschlange senden, zuletzt den Betrieb pruefen (Vorfaelle stehen im Log und gehen als T20 ans Team; die Pruefung
|
||||
* wirft nie und aendert das Ergebnis des Laufs nicht).
|
||||
* Haengt ein Teil, wird nur dieser im naechsten Takt uebersprungen (verfallUebersprungen bzw. mailUebersprungen im Ergebnis), der
|
||||
* andere laeuft weiter. { uebersprungen: true } kommt erst, wenn beide Teile uebersprungen werden.
|
||||
* @param {Date} now
|
||||
* @returns {Promise<{ verfallen: number, faellig: number, gesendet: number, fehler: number } | { aus: true } | { uebersprungen: true }>}
|
||||
* @returns {Promise<{ verfallen: number, faellig: number, gesendet: number, fehler: number, verfallUebersprungen?: true, mailUebersprungen?: true } | { aus: true } | { uebersprungen: true }>}
|
||||
*/
|
||||
export async function minutenJob(now) {
|
||||
return fuehreAus('minutenJob', now, async (lauf) => {
|
||||
const verfallen = await schritt(lauf, 'Verfall', () => verfalleHaltungen(lauf, now), 0)
|
||||
const { faellig, gesendet } = await schritt(lauf, 'Warteschlange', () => verarbeite(now), { faellig: 0, gesendet: 0 })
|
||||
// Zuletzt: die Pruefung sieht den Stand nach Verfall und Versand (ein eben gesendeter Eintrag staut nichts mehr)
|
||||
await schritt(lauf, 'Betriebsprüfung', () => pruefeBetrieb(now), [])
|
||||
return { verfallen, faellig, gesendet, fehler: lauf.fehler }
|
||||
})
|
||||
const [verfall, mail] = await Promise.all([
|
||||
fuehreAus(
|
||||
'minutenJob',
|
||||
now,
|
||||
async (lauf) => {
|
||||
const verfallen = await schritt(lauf, 'Verfall', () => verfalleHaltungen(lauf, now), 0)
|
||||
return { verfallen, fehler: lauf.fehler }
|
||||
},
|
||||
{ teil: 'Verfall' },
|
||||
),
|
||||
fuehreAus(
|
||||
'minutenJob',
|
||||
now,
|
||||
async (lauf) => {
|
||||
const { faellig, gesendet } = await schritt(lauf, 'Warteschlange', () => verarbeite(now), { faellig: 0, gesendet: 0 })
|
||||
// Zuletzt: die Pruefung sieht den Stand nach dem Versand (ein eben gesendeter Eintrag staut nichts mehr)
|
||||
await schritt(lauf, 'Betriebsprüfung', () => pruefeBetrieb(now), [])
|
||||
return { faellig, gesendet, fehler: lauf.fehler }
|
||||
},
|
||||
{ teil: 'Mailversand' },
|
||||
),
|
||||
])
|
||||
if (verfall.aus || mail.aus) return { aus: true }
|
||||
if (verfall.uebersprungen && mail.uebersprungen) return { uebersprungen: true }
|
||||
return {
|
||||
verfallen: verfall.verfallen ?? 0,
|
||||
faellig: mail.faellig ?? 0,
|
||||
gesendet: mail.gesendet ?? 0,
|
||||
fehler: (verfall.fehler ?? 0) + (mail.fehler ?? 0),
|
||||
...(verfall.uebersprungen ? { verfallUebersprungen: true } : {}),
|
||||
...(mail.uebersprungen ? { mailUebersprungen: true } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Fuenf-Minuten-Job
|
||||
|
||||
Reference in new issue
Block a user