Nightbot-Befund #99/#16 (P1): recoverStale gab "running"-Jobs nach starren
fünf Minuten wieder frei, ohne zu prüfen, ob der ursprüngliche Worker noch
aktiv daran arbeitet. Ein legitim länger laufender Job (z. B. ein
langsamer Connector-Abgleich) konnte dadurch von einem zweiten Worker
parallel erneut gestartet werden – Doppelausführung, z. B. doppelte
Provisionierung oder doppelter Mailversand.
- Migration 032: jobs.lease_id (pro Übernahme neu vergeben) und
jobs.locked_by (Worker-Kennung, nur Diagnose).
- runOnce vergibt beim Übernehmen einen frischen Lease und hält locked_at
per Heartbeat alle 60 s aktuell, solange der Handler läuft. Jedes
Abschluss-UPDATE (Erfolg wie Fehler) ist an genau diesen Lease gebunden
(WHERE lease_id = ?); hat recoverStale die Zeile inzwischen doch
freigegeben, verpufft ein verspätetes Ergebnis wirkungslos statt den
neuen Versuch zu überschreiben.
- recoverStale reißt jetzt nur noch Jobs an sich, deren Heartbeat
tatsächlich ausgeblieben ist (locked_at älter als 5 Minuten), nicht
mehr solche, die einfach nur lange laufen. Löscht lease_id beim
Freigeben mit.
Verifiziert: (a) SQL-Ebene direkt durchexerziert – frischer Heartbeat
verhindert das Anreißen, ausgebliebener Heartbeat löst es aus, ein
verspätetes Abschluss-UPDATE mit altem Lease betrifft 0 Zeilen; (b) echter
Code über runOnce/recoverStale gegen die Testdatenbank – Job durchläuft
Übernahme, Fehlschlag, lease-gebundenes Abschluss-UPDATE korrekt. Worker
neu gestartet, läuft fehlerfrei, 1437 bestehende Jobs weiterhin 'succeeded'.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>