diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index d5d71d9cb4..b2b99c99b3 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -39,76 +39,81 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Func predicate, CancellationToken cancellationToken = default) { - var registrations = _options.Value.Registrations; + async Task<(string registrationName, HealthReportEntry result)> RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration) + { + cancellationToken.ThrowIfCancellationRequested(); + var healthCheck = registration.Factory(scope.ServiceProvider); + + // If the health check does things like make Database queries using EF or backend HTTP calls, + // it may be valuable to know that logs it generates are part of a health check. So we start a scope. + using (_logger.BeginScope(new HealthCheckLogScope(registration.Name))) + { + var stopwatch = ValueStopwatch.StartNew(); + var context = new HealthCheckContext { Registration = registration }; + + Log.HealthCheckBegin(_logger, registration); + + HealthReportEntry entry; + try + { + var result = await healthCheck.CheckHealthAsync(context, cancellationToken); + var duration = stopwatch.GetElapsedTime(); + + entry = new HealthReportEntry( + status: result.Status, + description: result.Description, + duration: duration, + exception: result.Exception, + data: result.Data); + + Log.HealthCheckEnd(_logger, registration, entry, duration); + Log.HealthCheckData(_logger, registration, entry); + } + + // Allow cancellation to propagate. + catch (Exception ex) when (ex as OperationCanceledException == null) + { + var duration = stopwatch.GetElapsedTime(); + entry = new HealthReportEntry( + status: HealthStatus.Unhealthy, + description: ex.Message, + duration: duration, + exception: ex, + data: null); + + Log.HealthCheckError(_logger, registration, ex, duration); + } + + return (registration.Name, entry); + } + } + + IEnumerable registrations = _options.Value.Registrations; + if (predicate != null) + { + registrations = registrations.Where(predicate); + } + + var totalTime = ValueStopwatch.StartNew(); + Log.HealthCheckProcessingBegin(_logger); + + (string registrationName, HealthReportEntry result)[] results; using (var scope = _scopeFactory.CreateScope()) { - var context = new HealthCheckContext(); - var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); - - var totalTime = ValueStopwatch.StartNew(); - Log.HealthCheckProcessingBegin(_logger); - - foreach (var registration in registrations) - { - if (predicate != null && !predicate(registration)) - { - continue; - } - - cancellationToken.ThrowIfCancellationRequested(); - - var healthCheck = registration.Factory(scope.ServiceProvider); - - // If the health check does things like make Database queries using EF or backend HTTP calls, - // it may be valuable to know that logs it generates are part of a health check. So we start a scope. - using (_logger.BeginScope(new HealthCheckLogScope(registration.Name))) - { - var stopwatch = ValueStopwatch.StartNew(); - context.Registration = registration; - - Log.HealthCheckBegin(_logger, registration); - - HealthReportEntry entry; - try - { - var result = await healthCheck.CheckHealthAsync(context, cancellationToken); - var duration = stopwatch.GetElapsedTime(); - - entry = new HealthReportEntry( - status: result.Status, - description: result.Description, - duration: duration, - exception: result.Exception, - data: result.Data); - - Log.HealthCheckEnd(_logger, registration, entry, duration); - Log.HealthCheckData(_logger, registration, entry); - } - - // Allow cancellation to propagate. - catch (Exception ex) when (ex as OperationCanceledException == null) - { - var duration = stopwatch.GetElapsedTime(); - entry = new HealthReportEntry( - status: HealthStatus.Unhealthy, - description: ex.Message, - duration: duration, - exception: ex, - data: null); - - Log.HealthCheckError(_logger, registration, ex, duration); - } - - entries[registration.Name] = entry; - } - } - - var totalElapsedTime = totalTime.GetElapsedTime(); - var report = new HealthReport(entries, totalElapsedTime); - Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); - return report; + results = await Task.WhenAll(registrations.Select(r => RunCheckAsync(scope, r))); } + + var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var (registrationName, result) in results) + { + entries[registrationName] = result; + } + + var totalElapsedTime = totalTime.GetElapsedTime(); + var report = new HealthReport(entries, totalElapsedTime); + Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); + return report; } private static void ValidateRegistrations(IEnumerable registrations)