fix: Kafka consumer poison pill causes infinite retry
This commit is contained in:
@@ -54,6 +54,8 @@ public class EsIndexerService : BackgroundService
|
||||
|
||||
_logger.LogInformation("EsIndexerService started. Subscribed to 5 topics.");
|
||||
|
||||
var guard = new PoisonPillGuard("es-indexer", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -64,6 +66,7 @@ public class EsIndexerService : BackgroundService
|
||||
result = consumer.Consume(stoppingToken);
|
||||
await DispatchAsync(result.Topic, result.Message.Value, stoppingToken);
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -71,10 +74,15 @@ public class EsIndexerService : BackgroundService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (result is not null && guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex,
|
||||
"EsIndexer failed processing topic={Topic} offset={Offset} — not committing",
|
||||
"EsIndexer failed processing topic={Topic} offset={Offset} — will retry",
|
||||
result?.Topic, result?.Offset.Value);
|
||||
// Do not commit: message will be redelivered on restart
|
||||
await Task.Delay(1000, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public class GcsScoringService : BackgroundService
|
||||
|
||||
_logger.LogInformation("GcsScoringService started — consumer group: gcs-scoring");
|
||||
|
||||
var guard = new PoisonPillGuard("gcs-scoring", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -62,6 +64,7 @@ public class GcsScoringService : BackgroundService
|
||||
evt.EncounterId, outcome.TotalScore, outcome.Classification);
|
||||
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -69,8 +72,14 @@ public class GcsScoringService : BackgroundService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (result is not null && guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex,
|
||||
"GcsScoringService failed on topic={Topic} offset={Offset} — not committing",
|
||||
"GcsScoringService failed on topic={Topic} offset={Offset} — will retry",
|
||||
result?.Topic, result?.Offset.Value);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public class News2ScoringService : BackgroundService
|
||||
|
||||
_logger.LogInformation("News2ScoringService started — consumer group: news2-scoring");
|
||||
|
||||
var guard = new PoisonPillGuard("news2-scoring", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -62,6 +64,7 @@ public class News2ScoringService : BackgroundService
|
||||
evt.EncounterId, outcome.TotalScore, outcome.RiskLevel);
|
||||
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -69,8 +72,14 @@ public class News2ScoringService : BackgroundService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (result is not null && guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex,
|
||||
"News2ScoringService failed on topic={Topic} offset={Offset} — not committing",
|
||||
"News2ScoringService failed on topic={Topic} offset={Offset} — will retry",
|
||||
result?.Topic, result?.Offset.Value);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
}
|
||||
|
||||
+10
-2
@@ -50,6 +50,8 @@ public sealed class NotificationPublisherService : BackgroundService
|
||||
_logger.LogInformation("NotificationPublisherService started — consuming {Topics}",
|
||||
string.Join(", ", _kafkaOptions.Topics.AlertGenerated, _kafkaOptions.Topics.EncounterStatusChanged));
|
||||
|
||||
var guard = new PoisonPillGuard("notification-publisher", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -75,11 +77,17 @@ public sealed class NotificationPublisherService : BackgroundService
|
||||
await HandleEncounterStatusChangedAsync(chan, props, result.Message.Value, stoppingToken);
|
||||
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "NotificationPublisher failed to process message from {Topic}", result.Topic);
|
||||
// Do not commit — message will be reprocessed after consumer restart.
|
||||
if (guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex, "NotificationPublisher failed to process message from {Topic} — will retry", result.Topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ public class SepsisEngineService : BackgroundService
|
||||
_logger.LogInformation(
|
||||
"SepsisEngineService started — consumer group: sepsis-engine (qSOFA screening only)");
|
||||
|
||||
var guard = new PoisonPillGuard("sepsis-engine", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -68,6 +70,7 @@ public class SepsisEngineService : BackgroundService
|
||||
evt.EncounterId, evt.ObservationCode, evt.Value);
|
||||
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -75,8 +78,14 @@ public class SepsisEngineService : BackgroundService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (result is not null && guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex,
|
||||
"SepsisEngine failed on topic={Topic} offset={Offset} — not committing",
|
||||
"SepsisEngine failed on topic={Topic} offset={Offset} — will retry",
|
||||
result?.Topic, result?.Offset.Value);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public class SofaScoringService : BackgroundService
|
||||
|
||||
_logger.LogInformation("SofaScoringService started — consumer group: sofa-scoring");
|
||||
|
||||
var guard = new PoisonPillGuard("sofa-scoring", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -92,6 +94,7 @@ public class SofaScoringService : BackgroundService
|
||||
}
|
||||
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -99,8 +102,14 @@ public class SofaScoringService : BackgroundService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (result is not null && guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex,
|
||||
"SofaScoringService failed on topic={Topic} offset={Offset} — not committing",
|
||||
"SofaScoringService failed on topic={Topic} offset={Offset} — will retry",
|
||||
result?.Topic, result?.Offset.Value);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public class TrendAnalyzerService : BackgroundService
|
||||
|
||||
_logger.LogInformation("TrendAnalyzerService started — consumer group: trend-analyzer");
|
||||
|
||||
var guard = new PoisonPillGuard("trend-analyzer", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -63,6 +65,7 @@ public class TrendAnalyzerService : BackgroundService
|
||||
evt.EncounterId, outcome.ObservationCode, outcome.RatePerMinute);
|
||||
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -70,8 +73,14 @@ public class TrendAnalyzerService : BackgroundService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (result is not null && guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex,
|
||||
"TrendAnalyzerService failed on topic={Topic} offset={Offset} — not committing",
|
||||
"TrendAnalyzerService failed on topic={Topic} offset={Offset} — will retry",
|
||||
result?.Topic, result?.Offset.Value);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public class WarningAlertService : BackgroundService
|
||||
|
||||
_logger.LogInformation("WarningAlertService started — consumer group: warning-evaluator");
|
||||
|
||||
var guard = new PoisonPillGuard("warning-evaluator", _kafkaOptions.MaxPoisonRetries, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
@@ -58,6 +60,7 @@ public class WarningAlertService : BackgroundService
|
||||
stoppingToken);
|
||||
|
||||
consumer.Commit(result);
|
||||
guard.OnSuccess();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -65,8 +68,14 @@ public class WarningAlertService : BackgroundService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (result is not null && guard.ShouldSkip(result, ex))
|
||||
{
|
||||
consumer.Commit(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogError(ex,
|
||||
"WarningAlertService failed on topic={Topic} offset={Offset} — not committing",
|
||||
"WarningAlertService failed on topic={Topic} offset={Offset} — will retry",
|
||||
result?.Topic, result?.Offset.Value);
|
||||
await Task.Delay(2000, stoppingToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user