monitor/api/src/Sync/SyncSchedule.php
fds2610-bot ad14447092 Fehlgeschlagenen Codeberg-Sync zeitnah wiederholen statt bis zum naechsten Intervall zu warten
Auf der Produktionsmaschine traten vereinzelt Timeouts nach codeberg.org auf
("Operation timed out after 15003 milliseconds with 0 bytes received"), waehrend
die Verbindung sonst in ~0,1s antwortet.

Bisher fiel der Dauerlaeufer nach so einem Fehlschlag in den regulaeren Takt
zurueck. Weil SyncTrigger::consume() die Webhook-Anforderung schon vor dem Lauf
quittiert, war eine per Webhook angeforderte Aktualisierung damit verloren und
kam erst bis zu MONITOR_SYNC_INTERVAL Sekunden spaeter - genau die Verzoegerung,
die der Webhook abschaffen sollte.

Neu wird der naechste Versuch nach einem Fehlschlag vorgezogen: 5s, 10s, 20s, …
gedeckelt auf das regulaere Intervall (Sync\SyncSchedule). Ein kurzer Aussetzer
ist damit binnen Sekunden ueberstanden, eine laengere Codeberg-Stoerung laeuft
trotzdem nicht in einen Dauerbeschuss. Nach einem erfolgreichen Lauf gilt sofort
wieder das regulaere Intervall.

Die Wartezeit-Berechnung liegt in einer eigenen Klasse, weil die Schleife in
bin/sync-codeberg.php selbst nicht testbar ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:29:20 +02:00

43 lines
1.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Monitor\Api\Sync;
/**
* Bestimmt, wie lange der Sync-Dauerlaeufer (bin/sync-codeberg.php --watch) bis zum
* naechsten Lauf wartet.
*
* Nach einem erfolgreichen Lauf ist das schlicht MONITOR_SYNC_INTERVAL. Nach einem
* Fehlschlag wird der naechste Versuch dagegen deutlich vorgezogen: Ein per Webhook
* angeforderter Sync ist zu diesem Zeitpunkt bereits quittiert (SyncTrigger::consume()
* loescht die Anforderung vor dem Lauf), er wuerde sonst also erst nach vollen 600s
* nachgeholt - genau die Verzoegerung, die der Webhook abschaffen soll.
*
* Der Abstand waechst exponentiell (5s, 10s, 20s, …), gedeckelt auf das regulaere
* Intervall. Damit erholt sich ein kurzer Aussetzer (beobachtet: vereinzelte
* curl-Timeouts nach 15s ohne Antwort) binnen Sekunden, waehrend eine laengere
* Codeberg-Stoerung nicht in einen Dauerbeschuss laeuft.
*/
final class SyncSchedule
{
/** Ab hier waere 2^$failures groesser als jedes sinnvolle Intervall - schuetzt vor Overflow. */
private const MAX_EXPONENT = 20;
public function __construct(
private readonly int $intervalSeconds,
private readonly int $retryBaseSeconds = 5,
) {
}
public function secondsUntilNextRun(int $consecutiveFailures): int
{
if ($consecutiveFailures < 1) {
return $this->intervalSeconds;
}
$exponent = min($consecutiveFailures - 1, self::MAX_EXPONENT);
return min($this->intervalSeconds, $this->retryBaseSeconds * (2 ** $exponent));
}
}