Address health check review feedback
This commit is contained in:
parent
bcccb80ad5
commit
a0d6f6b24b
|
|
@ -186,7 +186,11 @@ app.MapHealthChecks("/health/ready", new()
|
|||
});
|
||||
app.MapHealthChecks("/", new()
|
||||
{
|
||||
Predicate = _ => false
|
||||
ResultStatusCodes =
|
||||
{
|
||||
[HealthStatus.Degraded] = StatusCodes.Status503ServiceUnavailable,
|
||||
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
|
||||
}
|
||||
});
|
||||
|
||||
// Routing used for SignalR.
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ public class ElsaWorkflowPersistenceHealthCheck(IServiceProvider serviceProvider
|
|||
/// <inheritdoc />
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var probeResults = await Task.WhenAll(
|
||||
ProbeAsync("workflow-definitions", serviceProvider.GetService<IWorkflowDefinitionStore>(), async (store, ct) => await store.FindAsync(new WorkflowDefinitionFilter { Id = ProbeId }, ct)),
|
||||
ProbeAsync("workflow-instances", serviceProvider.GetService<IWorkflowInstanceStore>(), async (store, ct) => await store.CountAsync(new WorkflowInstanceFilter { Id = ProbeId }, ct)),
|
||||
ProbeAsync("triggers", serviceProvider.GetService<ITriggerStore>(), async (store, ct) => await store.FindAsync(new TriggerFilter { Id = ProbeId }, ct)),
|
||||
ProbeAsync("bookmark-queue", serviceProvider.GetService<IBookmarkQueueStore>(), async (store, ct) => await store.FindAsync(new BookmarkQueueFilter { Id = ProbeId }, ct)));
|
||||
var probeResults = new List<ProbeResult>
|
||||
{
|
||||
await ProbeAsync("workflow-definitions", serviceProvider.GetService<IWorkflowDefinitionStore>(), async (store, ct) => await store.FindAsync(new WorkflowDefinitionFilter { Id = ProbeId }, ct)),
|
||||
await ProbeAsync("workflow-instances", serviceProvider.GetService<IWorkflowInstanceStore>(), async (store, ct) => await store.CountAsync(new WorkflowInstanceFilter { Id = ProbeId }, ct)),
|
||||
await ProbeAsync("triggers", serviceProvider.GetService<ITriggerStore>(), async (store, ct) => await store.FindAsync(new TriggerFilter { Id = ProbeId }, ct)),
|
||||
await ProbeAsync("bookmark-queue", serviceProvider.GetService<IBookmarkQueueStore>(), async (store, ct) => await store.FindAsync(new BookmarkQueueFilter { Id = ProbeId }, ct))
|
||||
};
|
||||
|
||||
var attemptedProbes = probeResults.Where(x => !x.Skipped).Select(x => x.StoreName).ToList();
|
||||
var successfulProbes = probeResults.Where(x => !x.Skipped && x.Exception == null).Select(x => x.StoreName).ToList();
|
||||
|
|
|
|||
|
|
@ -44,6 +44,25 @@ public class ElsaWorkflowPersistenceHealthCheckTests
|
|||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProbesStoresSequentially()
|
||||
{
|
||||
var tracker = new ProbeConcurrencyTracker();
|
||||
_workflowDefinitionStore.FindAsync(Arg.Any<WorkflowDefinitionFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(_ => TrackProbeAsync<Elsa.Workflows.Management.Entities.WorkflowDefinition?>(tracker, null));
|
||||
_workflowInstanceStore.CountAsync(Arg.Any<WorkflowInstanceFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(_ => new ValueTask<long>(TrackProbeAsync(tracker, 0L)));
|
||||
_triggerStore.FindAsync(Arg.Any<TriggerFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(_ => new ValueTask<Elsa.Workflows.Runtime.Entities.StoredTrigger?>(TrackProbeAsync<Elsa.Workflows.Runtime.Entities.StoredTrigger?>(tracker, null)));
|
||||
_bookmarkQueueStore.FindAsync(Arg.Any<BookmarkQueueFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(_ => TrackProbeAsync<Elsa.Workflows.Runtime.Entities.BookmarkQueueItem?>(tracker, null));
|
||||
|
||||
var result = await _sut.CheckHealthAsync(new HealthCheckContext());
|
||||
|
||||
Assert.Equal(HealthStatus.Healthy, result.Status);
|
||||
Assert.Equal(1, tracker.MaxConcurrentProbes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReturnsUnhealthyWithFailedStoreWhenAStoreProbeFails()
|
||||
{
|
||||
|
|
@ -92,4 +111,44 @@ public class ElsaWorkflowPersistenceHealthCheckTests
|
|||
Assert.Equal("workflow-definitions,workflow-instances,triggers,bookmark-queue", result.Data["skippedProbes"]);
|
||||
Assert.False(result.Data.ContainsKey("probes"));
|
||||
}
|
||||
|
||||
private static async Task<T> TrackProbeAsync<T>(ProbeConcurrencyTracker tracker, T result)
|
||||
{
|
||||
tracker.Enter();
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(10);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
tracker.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ProbeConcurrencyTracker
|
||||
{
|
||||
private int _currentProbes;
|
||||
private int _maxConcurrentProbes;
|
||||
|
||||
public int MaxConcurrentProbes => Volatile.Read(ref _maxConcurrentProbes);
|
||||
|
||||
public void Enter()
|
||||
{
|
||||
var currentProbes = Interlocked.Increment(ref _currentProbes);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var maxConcurrentProbes = MaxConcurrentProbes;
|
||||
if (currentProbes <= maxConcurrentProbes)
|
||||
return;
|
||||
|
||||
if (Interlocked.CompareExchange(ref _maxConcurrentProbes, currentProbes, maxConcurrentProbes) == maxConcurrentProbes)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit() => Interlocked.Decrement(ref _currentProbes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue