Improve SSE polling reliability
This commit is contained in:
@@ -138,6 +138,11 @@ public sealed class BackendClient
|
||||
settings.Backend.AgentId);
|
||||
}
|
||||
|
||||
// Das Backend sendet alle 30 Sekunden ein ": ping"-Keepalive. Kommt deutlich
|
||||
// länger nichts an, ist die Verbindung tot (z. B. halboffenes TCP nach Standby,
|
||||
// WLAN-Wechsel oder Server-Neustart) und muss neu aufgebaut werden.
|
||||
private static readonly TimeSpan SseReadTimeout = TimeSpan.FromSeconds(90);
|
||||
|
||||
public async Task WatchServerSentEventsAsync(Func<CancellationToken, Task> onLabelJobAvailable, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var settings = _settingsStore.Load();
|
||||
@@ -149,10 +154,33 @@ public sealed class BackendClient
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
using var reader = new StreamReader(stream);
|
||||
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested)
|
||||
|
||||
// Nachholen: Jobs abrufen, die entstanden sind, während keine SSE-Verbindung bestand.
|
||||
await onLabelJobAvailable(cancellationToken);
|
||||
|
||||
using var watchdog = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var line = await reader.ReadLineAsync(cancellationToken);
|
||||
if (line is null || !line.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||
watchdog.CancelAfter(SseReadTimeout);
|
||||
|
||||
string? line;
|
||||
try
|
||||
{
|
||||
line = await reader.ReadLineAsync(watchdog.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new TimeoutException(
|
||||
$"Seit {SseReadTimeout.TotalSeconds:F0} Sekunden keine Daten vom SSE-Stream empfangen – Verbindung gilt als tot.");
|
||||
}
|
||||
|
||||
if (line is null)
|
||||
{
|
||||
// Server hat den Stream geschlossen; Aufrufer baut die Verbindung neu auf.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!line.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -40,10 +40,9 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
|
||||
public async Task PollOnceAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!await _semaphore.WaitAsync(0, cancellationToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Auf einen laufenden Poll warten statt abzubrechen: ein SSE-Trigger, der
|
||||
// während eines Timer-Polls eintrifft, darf nicht verloren gehen.
|
||||
await _semaphore.WaitAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -178,7 +177,7 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
SetStatus(LastStatus.IsHealthy, "SSE-Verbindung wird aufgebaut.");
|
||||
await _backendClient.WatchServerSentEventsAsync(async token =>
|
||||
{
|
||||
SetStatus(true, "SSE: neuer Druckjob gemeldet.");
|
||||
SetStatus(true, "SSE: Druckaufträge werden abgerufen.");
|
||||
await PollOnceAsync(token);
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user