From 0c13fed11352e0348331d5e09ef99617cbf5ecb8 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 10 Jul 2022 23:18:07 +0100 Subject: [PATCH 01/91] Introduce HealthCheckRegistration periodicity --- .../src/HealthCheckRegistration.cs | 77 ++++++++++++++++++- .../HealthChecksBuilderAddCheckExtensions.cs | 6 +- .../src/HealthCheckPublisherHostedService.cs | 63 +++++++++++++-- 3 files changed, 133 insertions(+), 13 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index 573bd0fecd32..ce64449c152f 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -25,6 +25,7 @@ public sealed class HealthCheckRegistration private Func _factory; private string _name; private TimeSpan _timeout; + private TimeSpan _period; /// /// Creates a new for an existing instance. @@ -37,7 +38,24 @@ public sealed class HealthCheckRegistration /// /// A list of tags that can be used for filtering health checks. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, instance, failureStatus, tags, default) + : this(name, instance, failureStatus, tags, default, default) + { + } + + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// The instance. + /// + /// The that should be reported upon failure of the health check. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) + : this(name, instance, failureStatus, tags, timeout, default) { } @@ -52,7 +70,8 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) + /// An optional representing the individual period of the check. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? period) { if (name == null) { @@ -69,11 +88,17 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? throw new ArgumentOutOfRangeException(nameof(timeout)); } + if (period < TimeSpan.FromSeconds(1) && period != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + _name = name; FailureStatus = failureStatus ?? HealthStatus.Unhealthy; Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = (_) => instance; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; + Period = period ?? System.Threading.Timeout.InfiniteTimeSpan; } /// @@ -91,7 +116,7 @@ public HealthCheckRegistration( Func factory, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, factory, failureStatus, tags, default) + : this(name, factory, failureStatus, tags, default, default) { } @@ -112,6 +137,29 @@ public HealthCheckRegistration( HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) + : this(name, factory, failureStatus, tags, timeout, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// A delegate used to create the instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. + public HealthCheckRegistration( + string name, + Func factory, + HealthStatus? failureStatus, + IEnumerable? tags, + TimeSpan? timeout, + TimeSpan? period) { if (name == null) { @@ -128,11 +176,17 @@ public HealthCheckRegistration( throw new ArgumentOutOfRangeException(nameof(timeout)); } + if (period <= TimeSpan.Zero && period != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(timeout)); + } + _name = name; FailureStatus = failureStatus ?? HealthStatus.Unhealthy; Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = factory; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; + Period = period ?? System.Threading.Timeout.InfiniteTimeSpan; } /// @@ -174,6 +228,23 @@ public TimeSpan Timeout } } + /// + /// Gets or sets the individual period used for the check. + /// + public TimeSpan Period + { + get => _period; + set + { + if (value <= TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _period = value; + } + } + /// /// Gets or sets the health check name. /// diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 44f2436f1d00..9235691f565b 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -48,6 +48,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -56,7 +57,8 @@ public static IHealthChecksBuilder AddCheck( IHealthCheck instance, HealthStatus? failureStatus = null, IEnumerable? tags = null, - TimeSpan? timeout = null) + TimeSpan? timeout = null, + TimeSpan? period = null) { if (builder == null) { @@ -73,7 +75,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, period)); } /// diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index f96b2dbf324c..f5492286204d 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -16,17 +16,20 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; internal sealed partial class HealthCheckPublisherHostedService : IHostedService { private readonly HealthCheckService _healthCheckService; - private readonly IOptions _options; + private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; + private readonly IDictionary _healthCheckRegistrationsDictionary; private readonly CancellationTokenSource _stopping; private Timer? _timer; + private TimeSpan _timerElapsed = TimeSpan.Zero; private CancellationTokenSource? _runTokenSource; public HealthCheckPublisherHostedService( HealthCheckService healthCheckService, - IOptions options, + IOptions healthCheckPublisherOptions, + IOptions healthCheckServiceOptions, ILogger logger, IEnumerable publishers) { @@ -35,9 +38,14 @@ public HealthCheckPublisherHostedService( throw new ArgumentNullException(nameof(healthCheckService)); } - if (options == null) + if (healthCheckPublisherOptions == null) { - throw new ArgumentNullException(nameof(options)); + throw new ArgumentNullException(nameof(healthCheckPublisherOptions)); + } + + if (healthCheckServiceOptions == null) + { + throw new ArgumentNullException(nameof(healthCheckServiceOptions)); } if (logger == null) @@ -51,10 +59,22 @@ public HealthCheckPublisherHostedService( } _healthCheckService = healthCheckService; - _options = options; + _healthCheckPublisherOptions = healthCheckPublisherOptions; _logger = logger; _publishers = publishers.ToArray(); + _healthCheckRegistrationsDictionary = + healthCheckServiceOptions.Value.Registrations.ToDictionary(r => r.Name, r => + { + // For healthchecks with no individual period, default to publisher period + if (r.Period == Timeout.InfiniteTimeSpan) + { + r.Period = _healthCheckPublisherOptions.Value.Period; + } + + return r; + }); + _stopping = new CancellationTokenSource(); } @@ -71,7 +91,7 @@ public Task StartAsync(CancellationToken cancellationToken = default) // IMPORTANT - make sure this is the last thing that happens in this method. The timer can // fire before other code runs. - _timer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _options.Value.Delay, period: _options.Value.Period); + _timer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _healthCheckPublisherOptions.Value.Delay, period: _healthCheckPublisherOptions.Value.Period); return Task.CompletedTask; } @@ -119,7 +139,7 @@ internal async Task RunAsync() CancellationTokenSource? cancellation = null; try { - var timeout = _options.Value.Timeout; + var timeout = _healthCheckPublisherOptions.Value.Timeout; cancellation = CancellationTokenSource.CreateLinkedTokenSource(_stopping.Token); _runTokenSource = cancellation; @@ -150,8 +170,35 @@ private async Task RunAsyncCore(CancellationToken cancellationToken) // Forcibly yield - we want to unblock the timer thread. await Task.Yield(); + _timerElapsed += _healthCheckPublisherOptions.Value.Period; + + // Filter health check registrations per elapsed + var healthCheckRegistrationsElapsed = new HashSet(); + foreach (var registration in _healthCheckRegistrationsDictionary.Values) + { + if ((registration.Period - _timerElapsed).Ticks % registration.Period.Ticks == 0L) + { + healthCheckRegistrationsElapsed.Add(registration.Name); + System.Diagnostics.Debug.WriteLine($"Triggering {registration.Period} after {_timerElapsed.TotalSeconds} seconds"); + } + } + + // Concatenate with exposed predicate + var elapsedHealthCheckPredicate = (HealthCheckRegistration hcr) => + { + var runHealthCheck = healthCheckRegistrationsElapsed.Contains(hcr.Name); + + if (_healthCheckPublisherOptions.Value.Predicate == null) + { + return runHealthCheck; + } + + return _healthCheckPublisherOptions.Value.Predicate(hcr) + && runHealthCheck; + }; + // The health checks service does it's own logging, and doesn't throw exceptions. - var report = await _healthCheckService.CheckHealthAsync(_options.Value.Predicate, cancellationToken).ConfigureAwait(false); + var report = await _healthCheckService.CheckHealthAsync(elapsedHealthCheckPredicate, cancellationToken).ConfigureAwait(false); var publishers = _publishers; var tasks = new Task[publishers.Length]; From f9e52ee8c40f0669f2f8b894a8ae5ecb0baf9015 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 10 Jul 2022 23:25:45 +0100 Subject: [PATCH 02/91] Add missing public APIs contracts --- src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt | 4 ++++ src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..3314b931cb8a 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? period) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? period) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.set -> void diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..3a5d3bd5bc51 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From ac495ee00ea67e17a05101e2e98fd5750bba93eb Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 11 Jul 2022 15:07:04 +0100 Subject: [PATCH 03/91] Revert unshipped API --- src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 3a5d3bd5bc51..276e1353eba1 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, System.TimeSpan period, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From 51ed0b8875b7d553383eb2eaf2493c7d361d45a1 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 11 Jul 2022 15:08:06 +0100 Subject: [PATCH 04/91] Remove multiple blank lines --- src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index ce64449c152f..0503d096ec31 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -42,7 +42,6 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? { } - /// /// Creates a new for an existing instance. /// From 6796dc1fb4ec531d0e301acd8c7f42651d72357e Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 11 Jul 2022 15:09:25 +0100 Subject: [PATCH 05/91] Add missing AddTypeActivatedCheck param --- .../HealthChecksBuilderAddCheckExtensions.cs | 57 +++++++++++++++++-- .../HealthChecks/src/PublicAPI.Shipped.txt | 4 +- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 9235691f565b..fc77cf40854c 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -33,7 +33,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus, IEnumerable tags) { - return AddCheck(builder, name, instance, failureStatus, tags, default); + return AddCheck(builder, name, instance, failureStatus, tags, default, default); } /// @@ -103,7 +103,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus, IEnumerable tags) where T : class, IHealthCheck { - return AddCheck(builder, name, failureStatus, tags, default); + return AddCheck(builder, name, failureStatus, tags, default, default); } /// @@ -118,6 +118,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. /// The . /// /// This method will use to create the health check @@ -125,12 +126,14 @@ public static IHealthChecksBuilder AddCheck( /// with any lifetime it will be used. Otherwise an instance of type will be constructed with /// access to services from the dependency injection container. /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus = null, IEnumerable? tags = null, - TimeSpan? timeout = null) where T : class, IHealthCheck + TimeSpan? timeout = null, + TimeSpan? period = null) where T : class, IHealthCheck { if (builder == null) { @@ -142,7 +145,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout)); + return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, period)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] @@ -304,4 +307,50 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] T CreateInstance(IServiceProvider serviceProvider) => ActivatorUtilities.CreateInstance(serviceProvider, args); } + + /// + /// Adds a new type activated health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// Additional arguments to provide to the constructor. + /// A representing the timeout of the check. + /// An optional representing the individual period of the check. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. Additional arguments can be provided to the constructor via . + /// + public static IHealthChecksBuilder AddTypeActivatedCheck< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + IEnumerable tags, + TimeSpan timeout, + TimeSpan period, + params object[] args) where T : class, IHealthCheck + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, period)); + + [UnconditionalSuppressMessage("Trimming", "IL2091", + Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] + T CreateInstance(IServiceProvider serviceProvider) => ActivatorUtilities.CreateInstance(serviceProvider, args); + } } diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index a6dae5afb4a7..dede87ac6bcd 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -22,9 +22,9 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.HealthCheckServ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.HealthCheckServiceOptions() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.Registrations.get -> System.Collections.Generic.ICollection! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From e6ba33c6d023e8d3bfbecbb8c1edd2a9d130a5c3 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 11 Jul 2022 18:49:02 +0100 Subject: [PATCH 06/91] Fix period argument null check --- src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index 0503d096ec31..efcee48ae0a9 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -177,7 +177,7 @@ public HealthCheckRegistration( if (period <= TimeSpan.Zero && period != System.Threading.Timeout.InfiniteTimeSpan) { - throw new ArgumentOutOfRangeException(nameof(timeout)); + throw new ArgumentOutOfRangeException(nameof(period)); } _name = name; From 6b692faaeeed0c37704f159632c832a2f54c65ad Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 13 Jul 2022 02:27:37 +0100 Subject: [PATCH 07/91] Fix elapsed HC condition with timespan remainder --- .../HealthChecks/src/HealthCheckPublisherHostedService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index f5492286204d..1d533e051039 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -176,10 +176,9 @@ private async Task RunAsyncCore(CancellationToken cancellationToken) var healthCheckRegistrationsElapsed = new HashSet(); foreach (var registration in _healthCheckRegistrationsDictionary.Values) { - if ((registration.Period - _timerElapsed).Ticks % registration.Period.Ticks == 0L) + if (_timerElapsed.Ticks % registration.Period.Ticks == 0L) { healthCheckRegistrationsElapsed.Add(registration.Name); - System.Diagnostics.Debug.WriteLine($"Triggering {registration.Period} after {_timerElapsed.TotalSeconds} seconds"); } } From a1cae019f04cc80e5ba4005cc43ad95882adebd7 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 13 Jul 2022 16:10:41 +0200 Subject: [PATCH 08/91] Update HealthChecksBuilderDelegateExtensions api contract with period param --- .../HealthChecksBuilderDelegateExtensions.cs | 29 ++++++++++++------- .../HealthChecks/src/PublicAPI.Shipped.txt | 8 ++--- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 441e70152dac..2deb60113319 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -41,6 +41,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -48,7 +49,8 @@ public static IHealthChecksBuilder AddCheck( string name, Func check, IEnumerable? tags = null, - TimeSpan? timeout = default) + TimeSpan? timeout = default, + TimeSpan? period = default) { if (builder == null) { @@ -66,7 +68,7 @@ public static IHealthChecksBuilder AddCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); } /// @@ -84,7 +86,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags) { - return AddCheck(builder, name, check, tags, default); + return AddCheck(builder, name, check, tags, default, default); } /// @@ -95,6 +97,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -102,7 +105,8 @@ public static IHealthChecksBuilder AddCheck( string name, Func check, IEnumerable? tags = null, - TimeSpan? timeout = default) + TimeSpan? timeout = default, + TimeSpan? period = default) { if (builder == null) { @@ -120,7 +124,7 @@ public static IHealthChecksBuilder AddCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); } /// @@ -149,6 +153,7 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( @@ -156,7 +161,8 @@ public static IHealthChecksBuilder AddAsyncCheck( string name, Func> check, IEnumerable? tags = null, - TimeSpan? timeout = default) + TimeSpan? timeout = default, + TimeSpan? period = default) { if (builder == null) { @@ -174,7 +180,7 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); } /// @@ -192,7 +198,7 @@ public static IHealthChecksBuilder AddAsyncCheck( Func> check, IEnumerable tags) { - return AddAsyncCheck(builder, name, check, tags, default); + return AddAsyncCheck(builder, name, check, tags, default, default); } /// @@ -203,6 +209,8 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. + /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( @@ -210,7 +218,8 @@ public static IHealthChecksBuilder AddAsyncCheck( string name, Func> check, IEnumerable? tags = null, - TimeSpan? timeout = default) + TimeSpan? timeout = default, + TimeSpan? period = default) { if (builder == null) { @@ -228,6 +237,6 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); } } diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index dede87ac6bcd..6543d9fab8f7 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -31,11 +31,11 @@ static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExten static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From a94160b28dcc98fe97cc4371ae37a1c46d4eda6b Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 14 Jul 2022 00:41:06 +0200 Subject: [PATCH 09/91] Update split publish from report generation --- .../src/HealthCheckPublisherHostedService.cs | 91 +++++++++++-------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 1d533e051039..7458d1257914 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -17,13 +17,15 @@ internal sealed partial class HealthCheckPublisherHostedService : IHostedService { private readonly HealthCheckService _healthCheckService; private readonly IOptions _healthCheckPublisherOptions; + private readonly IOptions _healthCheckServiceOptions; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; private readonly IDictionary _healthCheckRegistrationsDictionary; + private readonly Queue _healthReportQueue; private readonly CancellationTokenSource _stopping; - private Timer? _timer; - private TimeSpan _timerElapsed = TimeSpan.Zero; + private Timer? _healthCheckPublisherTimer; + private ICollection _healthCheckRegistrationTimers; private CancellationTokenSource? _runTokenSource; public HealthCheckPublisherHostedService( @@ -60,6 +62,7 @@ public HealthCheckPublisherHostedService( _healthCheckService = healthCheckService; _healthCheckPublisherOptions = healthCheckPublisherOptions; + _healthCheckServiceOptions = healthCheckServiceOptions; _logger = logger; _publishers = publishers.ToArray(); @@ -75,12 +78,14 @@ public HealthCheckPublisherHostedService( return r; }); + _healthReportQueue = new Queue(); + _stopping = new CancellationTokenSource(); } internal bool IsStopping => _stopping.IsCancellationRequested; - internal bool IsTimerRunning => _timer != null; + internal bool IsTimerRunning => _healthCheckPublisherTimer != null; public Task StartAsync(CancellationToken cancellationToken = default) { @@ -89,9 +94,18 @@ public Task StartAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } + _healthCheckRegistrationTimers = + _healthCheckRegistrationsDictionary + .Select(r => + NonCapturingTimer.Create( + async (state) => await CheckHealthAsync(r.Value.Name).ConfigureAwait(false), + null, + dueTime: _healthCheckPublisherOptions.Value.Delay, + period: r.Value.Period)).ToList(); + // IMPORTANT - make sure this is the last thing that happens in this method. The timer can // fire before other code runs. - _timer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _healthCheckPublisherOptions.Value.Delay, period: _healthCheckPublisherOptions.Value.Period); + _healthCheckPublisherTimer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _healthCheckPublisherOptions.Value.Delay, period: _healthCheckPublisherOptions.Value.Period); return Task.CompletedTask; } @@ -112,8 +126,14 @@ public Task StopAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - _timer?.Dispose(); - _timer = null; + _healthCheckPublisherTimer?.Dispose(); + _healthCheckPublisherTimer = null; + + foreach (var timer in _healthCheckRegistrationTimers) + { + timer.Dispose(); + } + _healthCheckRegistrationTimers = default; return Task.CompletedTask; } @@ -130,6 +150,28 @@ internal void CancelToken() _runTokenSource!.Cancel(); } + private async Task CheckHealthAsync(string healthCheckName) + { + // Concatenate with exposed predicate + var runHealthCheckPredicate = (HealthCheckRegistration hcr) => + { + var runHealthCheck = hcr.Name == healthCheckName; + + if (_healthCheckPublisherOptions.Value.Predicate == null) + { + return runHealthCheck; + } + + return _healthCheckPublisherOptions.Value.Predicate(hcr) + && runHealthCheck; + }; + + // The health checks service does it's own logging, and doesn't throw exceptions. + var report = await _healthCheckService.CheckHealthAsync(runHealthCheckPredicate).ConfigureAwait(false); + + _healthReportQueue.Enqueue(report); + } + // Internal for testing internal async Task RunAsync() { @@ -170,42 +212,19 @@ private async Task RunAsyncCore(CancellationToken cancellationToken) // Forcibly yield - we want to unblock the timer thread. await Task.Yield(); - _timerElapsed += _healthCheckPublisherOptions.Value.Period; + // The health checks service does it's own logging, and doesn't throw exceptions. + var publishers = _publishers; + var tasks = new List(publishers.Length * _healthReportQueue.Count); - // Filter health check registrations per elapsed - var healthCheckRegistrationsElapsed = new HashSet(); - foreach (var registration in _healthCheckRegistrationsDictionary.Values) + while (_healthReportQueue.Any()) { - if (_timerElapsed.Ticks % registration.Period.Ticks == 0L) + var report = _healthReportQueue.Dequeue(); + foreach (var publisher in publishers) { - healthCheckRegistrationsElapsed.Add(registration.Name); + tasks.Add(RunPublisherAsync(publisher, report, cancellationToken)); } } - // Concatenate with exposed predicate - var elapsedHealthCheckPredicate = (HealthCheckRegistration hcr) => - { - var runHealthCheck = healthCheckRegistrationsElapsed.Contains(hcr.Name); - - if (_healthCheckPublisherOptions.Value.Predicate == null) - { - return runHealthCheck; - } - - return _healthCheckPublisherOptions.Value.Predicate(hcr) - && runHealthCheck; - }; - - // The health checks service does it's own logging, and doesn't throw exceptions. - var report = await _healthCheckService.CheckHealthAsync(elapsedHealthCheckPredicate, cancellationToken).ConfigureAwait(false); - - var publishers = _publishers; - var tasks = new Task[publishers.Length]; - for (var i = 0; i < publishers.Length; i++) - { - tasks[i] = RunPublisherAsync(publishers[i], report, cancellationToken); - } - await Task.WhenAll(tasks).ConfigureAwait(false); } From 3d7999257d5d74cd72823304014d13ded6928b3a Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 14 Jul 2022 15:12:29 +0200 Subject: [PATCH 10/91] Revert HealthCheckPublisherHostedService --- .../src/HealthCheckPublisherHostedService.cs | 97 +++---------------- 1 file changed, 16 insertions(+), 81 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 7458d1257914..f96b2dbf324c 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -16,22 +16,17 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; internal sealed partial class HealthCheckPublisherHostedService : IHostedService { private readonly HealthCheckService _healthCheckService; - private readonly IOptions _healthCheckPublisherOptions; - private readonly IOptions _healthCheckServiceOptions; + private readonly IOptions _options; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; - private readonly IDictionary _healthCheckRegistrationsDictionary; - private readonly Queue _healthReportQueue; private readonly CancellationTokenSource _stopping; - private Timer? _healthCheckPublisherTimer; - private ICollection _healthCheckRegistrationTimers; + private Timer? _timer; private CancellationTokenSource? _runTokenSource; public HealthCheckPublisherHostedService( HealthCheckService healthCheckService, - IOptions healthCheckPublisherOptions, - IOptions healthCheckServiceOptions, + IOptions options, ILogger logger, IEnumerable publishers) { @@ -40,14 +35,9 @@ public HealthCheckPublisherHostedService( throw new ArgumentNullException(nameof(healthCheckService)); } - if (healthCheckPublisherOptions == null) + if (options == null) { - throw new ArgumentNullException(nameof(healthCheckPublisherOptions)); - } - - if (healthCheckServiceOptions == null) - { - throw new ArgumentNullException(nameof(healthCheckServiceOptions)); + throw new ArgumentNullException(nameof(options)); } if (logger == null) @@ -61,31 +51,16 @@ public HealthCheckPublisherHostedService( } _healthCheckService = healthCheckService; - _healthCheckPublisherOptions = healthCheckPublisherOptions; - _healthCheckServiceOptions = healthCheckServiceOptions; + _options = options; _logger = logger; _publishers = publishers.ToArray(); - _healthCheckRegistrationsDictionary = - healthCheckServiceOptions.Value.Registrations.ToDictionary(r => r.Name, r => - { - // For healthchecks with no individual period, default to publisher period - if (r.Period == Timeout.InfiniteTimeSpan) - { - r.Period = _healthCheckPublisherOptions.Value.Period; - } - - return r; - }); - - _healthReportQueue = new Queue(); - _stopping = new CancellationTokenSource(); } internal bool IsStopping => _stopping.IsCancellationRequested; - internal bool IsTimerRunning => _healthCheckPublisherTimer != null; + internal bool IsTimerRunning => _timer != null; public Task StartAsync(CancellationToken cancellationToken = default) { @@ -94,18 +69,9 @@ public Task StartAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - _healthCheckRegistrationTimers = - _healthCheckRegistrationsDictionary - .Select(r => - NonCapturingTimer.Create( - async (state) => await CheckHealthAsync(r.Value.Name).ConfigureAwait(false), - null, - dueTime: _healthCheckPublisherOptions.Value.Delay, - period: r.Value.Period)).ToList(); - // IMPORTANT - make sure this is the last thing that happens in this method. The timer can // fire before other code runs. - _healthCheckPublisherTimer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _healthCheckPublisherOptions.Value.Delay, period: _healthCheckPublisherOptions.Value.Period); + _timer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _options.Value.Delay, period: _options.Value.Period); return Task.CompletedTask; } @@ -126,14 +92,8 @@ public Task StopAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - _healthCheckPublisherTimer?.Dispose(); - _healthCheckPublisherTimer = null; - - foreach (var timer in _healthCheckRegistrationTimers) - { - timer.Dispose(); - } - _healthCheckRegistrationTimers = default; + _timer?.Dispose(); + _timer = null; return Task.CompletedTask; } @@ -150,28 +110,6 @@ internal void CancelToken() _runTokenSource!.Cancel(); } - private async Task CheckHealthAsync(string healthCheckName) - { - // Concatenate with exposed predicate - var runHealthCheckPredicate = (HealthCheckRegistration hcr) => - { - var runHealthCheck = hcr.Name == healthCheckName; - - if (_healthCheckPublisherOptions.Value.Predicate == null) - { - return runHealthCheck; - } - - return _healthCheckPublisherOptions.Value.Predicate(hcr) - && runHealthCheck; - }; - - // The health checks service does it's own logging, and doesn't throw exceptions. - var report = await _healthCheckService.CheckHealthAsync(runHealthCheckPredicate).ConfigureAwait(false); - - _healthReportQueue.Enqueue(report); - } - // Internal for testing internal async Task RunAsync() { @@ -181,7 +119,7 @@ internal async Task RunAsync() CancellationTokenSource? cancellation = null; try { - var timeout = _healthCheckPublisherOptions.Value.Timeout; + var timeout = _options.Value.Timeout; cancellation = CancellationTokenSource.CreateLinkedTokenSource(_stopping.Token); _runTokenSource = cancellation; @@ -213,16 +151,13 @@ private async Task RunAsyncCore(CancellationToken cancellationToken) await Task.Yield(); // The health checks service does it's own logging, and doesn't throw exceptions. - var publishers = _publishers; - var tasks = new List(publishers.Length * _healthReportQueue.Count); + var report = await _healthCheckService.CheckHealthAsync(_options.Value.Predicate, cancellationToken).ConfigureAwait(false); - while (_healthReportQueue.Any()) + var publishers = _publishers; + var tasks = new Task[publishers.Length]; + for (var i = 0; i < publishers.Length; i++) { - var report = _healthReportQueue.Dequeue(); - foreach (var publisher in publishers) - { - tasks.Add(RunPublisherAsync(publisher, report, cancellationToken)); - } + tasks[i] = RunPublisherAsync(publishers[i], report, cancellationToken); } await Task.WhenAll(tasks).ConfigureAwait(false); From fc43049ce9ec0be58ea7549dbf07ae57f0462910 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 14 Jul 2022 17:44:21 +0200 Subject: [PATCH 11/91] Introduce periodicity and report aggregation in DefaultHealthCheckService --- .../src/DefaultHealthCheckService.cs | 83 ++++++++++++++----- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 45e8a8963d72..501ec9e94220 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -3,6 +3,7 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -20,58 +21,102 @@ internal sealed partial class DefaultHealthCheckService : HealthCheckService private readonly IServiceScopeFactory _scopeFactory; private readonly IOptions _options; private readonly ILogger _logger; + private ConcurrentQueue> _healthReportQueue; + private IReadOnlyDictionary> _timeToHCs; + private ICollection _healthCheckRegistrationTimers; + private ICollection _healthCheckRegistrationsDefault; public DefaultHealthCheckService( IServiceScopeFactory scopeFactory, - IOptions options, + IOptions _healthCheckServiceOptions, + IOptions _healthCheckPublisherOptions, ILogger logger) { _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); - _options = options ?? throw new ArgumentNullException(nameof(options)); + _options = _healthCheckServiceOptions ?? throw new ArgumentNullException(nameof(_healthCheckServiceOptions)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); // We're specifically going out of our way to do this at startup time. We want to make sure you // get any kind of health-check related error as early as possible. Waiting until someone // actually tries to **run** health checks would be real baaaaad. ValidateRegistrations(_options.Value.Registrations); - } - public override async Task CheckHealthAsync( - Func? predicate, - CancellationToken cancellationToken = default) - { - var registrations = _options.Value.Registrations; - if (predicate != null) - { - registrations = registrations.Where(predicate).ToArray(); - } - var totalTime = ValueStopwatch.StartNew(); - Log.HealthCheckProcessingBegin(_logger); + _healthReportQueue = new ConcurrentQueue>(); + + // TODO add predicate + _healthCheckRegistrationsDefault = _options.Value.Registrations.Where(r => r.Period == Timeout.InfiniteTimeSpan).ToList(); + + _timeToHCs = (from r in _options.Value.Registrations + where r.Period != Timeout.InfiniteTimeSpan // Skip HCs with no individual period + group r by r.Period).ToDictionary(g => g.Key, g => g.ToList()); + + _healthCheckRegistrationTimers = _timeToHCs.Select(t => + NonCapturingTimer.Create( + async (state) => + { + var entries = await CheckHealthAsyncCore(t.Value).ConfigureAwait(false); + foreach (var entry in entries) + { + _healthReportQueue.Enqueue(entry); + } + }, + null, + dueTime: _healthCheckPublisherOptions.Value.Delay, + period: t.Key)).ToList(); + } + private async Task>> CheckHealthAsyncCore(ICollection registrations) + { var tasks = new Task[registrations.Count]; var index = 0; foreach (var registration in registrations) { - tasks[index++] = Task.Run(() => RunCheckAsync(registration, cancellationToken), cancellationToken); + tasks[index++] = Task.Run(() => RunCheckAsync(registration)); } await Task.WhenAll(tasks).ConfigureAwait(false); + // StringComparer.OrdinalIgnoreCase index = 0; - var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); + var entries = new List>(); foreach (var registration in registrations) { - entries[registration.Name] = tasks[index++].Result; + entries.Add(new KeyValuePair(registration.Name, tasks[index++].Result)); + } + + return entries; + } + + public override async Task CheckHealthAsync( + Func? predicate, + CancellationToken cancellationToken = default) + { + var totalTime = ValueStopwatch.StartNew(); + Log.HealthCheckProcessingBegin(_logger); + + var registrations = _options.Value.Registrations; + if (predicate != null) + { + registrations = registrations.Where(predicate).ToArray(); + } + + var healthReportEntriesValues = await CheckHealthAsyncCore(registrations).ConfigureAwait(false); // Run default + while (_healthReportQueue.TryDequeue(out var entry)) + { + healthReportEntriesValues.Add(entry); } + var healthReportEntries = healthReportEntriesValues.ToDictionary(kv => kv.Key, kv => kv.Value); + var totalElapsedTime = totalTime.GetElapsedTime(); - var report = new HealthReport(entries, totalElapsedTime); + var report = new HealthReport(healthReportEntries, totalElapsedTime); Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); + return report; } - private async Task RunCheckAsync(HealthCheckRegistration registration, CancellationToken cancellationToken) + private async Task RunCheckAsync(HealthCheckRegistration registration, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); From d72b1135ac67f111c3c1312276a5dfa176193434 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 14 Jul 2022 17:45:05 +0200 Subject: [PATCH 12/91] Update unit test with required HealthCheckPublisherOptions --- .../HealthChecks/test/DefaultHealthCheckServiceTest.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 735f228898e6..b6a599e3a931 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -40,11 +40,12 @@ public void Constructor_ThrowsUsefulExceptionForDuplicateNames() var services = serviceCollection.BuildServiceProvider(); var scopeFactory = services.GetRequiredService(); - var options = services.GetRequiredService>(); + var healthCheckServiceOptionsOptions = services.GetRequiredService>(); + var healthCheckPublisherOptions = services.GetRequiredService>(); var logger = services.GetRequiredService>(); // Act - var exception = Assert.Throws(() => new DefaultHealthCheckService(scopeFactory, options, logger)); + var exception = Assert.Throws(() => new DefaultHealthCheckService(scopeFactory, healthCheckServiceOptionsOptions, healthCheckPublisherOptions, logger)); // Assert Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message); From efbd13f059f2ad93b1db1a23f932d4897c848762 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Fri, 15 Jul 2022 19:06:12 +0200 Subject: [PATCH 13/91] Fix cancellation and add predicate for individual HCs --- .../src/DefaultHealthCheckService.cs | 137 ++++++++++-------- 1 file changed, 77 insertions(+), 60 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 501ec9e94220..6d1ae16eb97f 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -19,101 +19,102 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; internal sealed partial class DefaultHealthCheckService : HealthCheckService { private readonly IServiceScopeFactory _scopeFactory; - private readonly IOptions _options; + private readonly IOptions _healthCheckServiceOptions; + private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; - private ConcurrentQueue> _healthReportQueue; - private IReadOnlyDictionary> _timeToHCs; - private ICollection _healthCheckRegistrationTimers; - private ICollection _healthCheckRegistrationsDefault; + private readonly ConcurrentQueue> _healthReportEntriesQueue; + private readonly IReadOnlyDictionary> _periodHealthChecksMap; + private readonly ICollection _healthCheckRegistrationTimers; public DefaultHealthCheckService( IServiceScopeFactory scopeFactory, - IOptions _healthCheckServiceOptions, - IOptions _healthCheckPublisherOptions, + IOptions healthCheckServiceOptions, + IOptions healthCheckPublisherOptions, ILogger logger) { _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); - _options = _healthCheckServiceOptions ?? throw new ArgumentNullException(nameof(_healthCheckServiceOptions)); + _healthCheckServiceOptions = healthCheckServiceOptions ?? throw new ArgumentNullException(nameof(healthCheckServiceOptions)); + _healthCheckPublisherOptions = healthCheckPublisherOptions ?? throw new ArgumentNullException(nameof(healthCheckPublisherOptions)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); // We're specifically going out of our way to do this at startup time. We want to make sure you // get any kind of health-check related error as early as possible. Waiting until someone // actually tries to **run** health checks would be real baaaaad. - ValidateRegistrations(_options.Value.Registrations); + ValidateRegistrations(_healthCheckServiceOptions.Value.Registrations); - _healthReportQueue = new ConcurrentQueue>(); + _healthReportEntriesQueue = new ConcurrentQueue>(); - // TODO add predicate - _healthCheckRegistrationsDefault = _options.Value.Registrations.Where(r => r.Period == Timeout.InfiniteTimeSpan).ToList(); + _periodHealthChecksMap = (from r in _healthCheckServiceOptions.Value.Registrations + where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publisher predicate + where r.Period != Timeout.InfiniteTimeSpan // Skip HCs with no individual period + group r by r.Period).ToDictionary, TimeSpan, List>(g => g.Key, g => g.ToList()); - _timeToHCs = (from r in _options.Value.Registrations - where r.Period != Timeout.InfiniteTimeSpan // Skip HCs with no individual period - group r by r.Period).ToDictionary(g => g.Key, g => g.ToList()); - - _healthCheckRegistrationTimers = _timeToHCs.Select(t => + _healthCheckRegistrationTimers = _periodHealthChecksMap.AsParallel().Select(t => NonCapturingTimer.Create( - async (state) => - { - var entries = await CheckHealthAsyncCore(t.Value).ConfigureAwait(false); - foreach (var entry in entries) - { - _healthReportQueue.Enqueue(entry); - } - }, - null, - dueTime: _healthCheckPublisherOptions.Value.Delay, - period: t.Key)).ToList(); + async (state) => + { + var entries = await RunChecksAsync(t.Value).ConfigureAwait(false); + foreach (var entry in entries) + { + _healthReportEntriesQueue.Enqueue(entry); + } + }, + null, + dueTime: healthCheckPublisherOptions.Value.Delay, + period: t.Key)).ToList(); } - private async Task>> CheckHealthAsyncCore(ICollection registrations) + public override async Task CheckHealthAsync(Func? predicate, CancellationToken cancellationToken = default) { - var tasks = new Task[registrations.Count]; - var index = 0; + // TODO: if we decide for the current design, where HCs with default periodicity are initiated during CheckHealthAsync, + // individual-periodicity HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService) - foreach (var registration in registrations) - { - tasks[index++] = Task.Run(() => RunCheckAsync(registration)); - } + var totalTime = ValueStopwatch.StartNew(); + Log.HealthCheckProcessingBegin(_logger); - await Task.WhenAll(tasks).ConfigureAwait(false); + var registrations = _healthCheckServiceOptions.Value.Registrations.Where(r => ByPredicate(r, predicate)).ToList(); - // StringComparer.OrdinalIgnoreCase - index = 0; - var entries = new List>(); - foreach (var registration in registrations) + // Run healthchecks with default/publisher period + var healthReportEntriesValues = await RunChecksAsync(registrations, cancellationToken).ConfigureAwait(false); + + // Collect health report entries from previously run periodic healthchecks within the default period + while (_healthReportEntriesQueue.TryDequeue(out var entry)) { - entries.Add(new KeyValuePair(registration.Name, tasks[index++].Result)); + healthReportEntriesValues.Add(entry); } - return entries; + var healthReportEntriesDictionary = new Dictionary( + healthReportEntriesValues.ToDictionary(kv => kv.Key, kv => kv.Value), + StringComparer.OrdinalIgnoreCase); + + var totalElapsedTime = totalTime.GetElapsedTime(); + var report = new HealthReport(healthReportEntriesDictionary, totalElapsedTime); + Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); + + return report; } - public override async Task CheckHealthAsync( - Func? predicate, - CancellationToken cancellationToken = default) + private async Task>> RunChecksAsync(ICollection registrations, CancellationToken cancellationToken = default) { - var totalTime = ValueStopwatch.StartNew(); - Log.HealthCheckProcessingBegin(_logger); + var tasks = new Task[registrations.Count]; + var index = 0; - var registrations = _options.Value.Registrations; - if (predicate != null) + foreach (var registration in registrations) { - registrations = registrations.Where(predicate).ToArray(); + tasks[index++] = Task.Run(() => RunCheckAsync(registration, cancellationToken), cancellationToken); } - var healthReportEntriesValues = await CheckHealthAsyncCore(registrations).ConfigureAwait(false); // Run default - while (_healthReportQueue.TryDequeue(out var entry)) - { - healthReportEntriesValues.Add(entry); - } + await Task.WhenAll(tasks).ConfigureAwait(false); - var healthReportEntries = healthReportEntriesValues.ToDictionary(kv => kv.Key, kv => kv.Value); + var entries = new List>(); - var totalElapsedTime = totalTime.GetElapsedTime(); - var report = new HealthReport(healthReportEntries, totalElapsedTime); - Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); + index = 0; + foreach (var registration in registrations) + { + entries.Add(new KeyValuePair(registration.Name, tasks[index++].Result)); + } - return report; + return entries; } private async Task RunCheckAsync(HealthCheckRegistration registration, CancellationToken cancellationToken = default) @@ -225,6 +226,22 @@ private static void ValidateRegistrations(IEnumerable r } } + private bool ByPredicate(HealthCheckRegistration registration, Func? predicate) + { + if (predicate == null) + { + predicate = _healthCheckPublisherOptions.Value.Predicate; + } + + // Default to the HealthCheckPublisher predicate + if (predicate == null) + { + return true; + } + + return predicate(registration); + } + internal static class EventIds { public const int HealthCheckProcessingBeginId = 100; From f82729651fc2c959d74f21599fca652e949bcf7d Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Fri, 15 Jul 2022 19:10:18 +0200 Subject: [PATCH 14/91] Add unit tests for individual periodicity HCs --- .../test/DefaultHealthCheckServiceTest.cs | 301 +++++++++++++++++- 1 file changed, 300 insertions(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index b6a599e3a931..d6659d39345c 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -116,6 +116,174 @@ public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() }); } + [Fact] + public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesResultsAsync() + { + const string DataKey = "Foo"; + const string DataValue = "Bar"; + const string HealthyMessage = "Everything is A-OK"; + var healthyCheckTags = new List { "healthy-check-tag" }; + + // Arrange + var data = new Dictionary() + { + { DataKey, DataValue } + }; + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("Check1Period3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(3)); + b.AddAsyncCheck("Check2Period7", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(7)); + b.AddAsyncCheck("Check3Period12", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(12)); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries.OrderBy(kvp => kvp.Key), + actual => + { + Assert.Equal("Check1Period3", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check2Period7", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check3Period12", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }); + } + + [Fact] + public async Task CheckAsync_MixedPublisherIndividualPeriodicity_RunsAllChecksAndAggregatesResultsAsync() + { + const string DataKey = "Foo"; + const string DataValue = "Bar"; + const string HealthyMessage = "Everything is A-OK"; + var healthyCheckTags = new List { "healthy-check-tag" }; + + // Arrange + var data = new Dictionary() + { + { DataKey, DataValue } + }; + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("Check1PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("Check2PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("Check3Period3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(3)); + b.AddAsyncCheck("Check4Period7", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(7)); + b.AddAsyncCheck("Check5Period12", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(12)); + }, + options => + { + options.Period = TimeSpan.FromSeconds(2); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries.OrderBy(kvp => kvp.Key), + actual => + { + Assert.Equal("Check1PeriodDefault", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check2PeriodDefault", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check3Period3", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check4Period7", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check5Period12", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }); + } + [Fact] public async Task CheckAsync_TagsArePresentInHealthReportEntryIfExceptionOccurs() { @@ -199,6 +367,62 @@ public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync() }); } + [Fact] + public async Task CheckAsync_IndividualPeriodicity_RunsFilteredChecksThroughPredicateAndAggregatesResultsAsync() + { + const string DataKey = "Foo"; + const string DataValue = "Bar"; + const string HealthyMessage = "Everything is A-OK"; + + // Arrange + var data = new Dictionary + { + { DataKey, DataValue } + }; + + // TODO: currently periodic HCs initiated in the cctor use the default predicate instead of the one passed in CheckHealthAsync + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("PredicateCheck1", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), period: TimeSpan.FromSeconds(2)); + b.AddAsyncCheck("PredicateCheck2", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), period: TimeSpan.FromSeconds(7)); + b.AddAsyncCheck("Check3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), period: TimeSpan.FromSeconds(11)); + }, + options => + { + options.Predicate = r => r.Name.Contains("Predicate"); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection(results.Entries, + actual => + { + Assert.Equal("PredicateCheck1", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + }, + actual => + { + Assert.Equal("PredicateCheck2", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + }); + } + [Fact] public async Task CheckHealthAsync_SetsRegistrationForEachCheck() { @@ -271,6 +495,43 @@ public async Task CheckHealthAsync_Cancellation_CanPropagate() await Assert.ThrowsAsync(async () => await task); } + [Fact] + public async Task CheckHealthAsync_IndividualPeriodicity_Cancellation_CanPropagate() + { + // Arrange + var insideCheck = new TaskCompletionSource(); + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("cancels1", async ct => + { + insideCheck.SetResult(null); + + await Task.Delay(10000, ct); + return HealthCheckResult.Unhealthy(); + }, period: TimeSpan.FromSeconds(5)); + + b.AddAsyncCheck("cancels2", async ct => + { + insideCheck.SetResult(null); + + await Task.Delay(10000, ct); + return HealthCheckResult.Unhealthy(); + }, period: TimeSpan.FromSeconds(7)); + }); + + var cancel = new CancellationTokenSource(); + var task = service.CheckHealthAsync(cancel.Token); + + // After this returns we know the check has started + await insideCheck.Task; + + cancel.Cancel(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await task); + } + [Fact] public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResultAsync() { @@ -590,6 +851,39 @@ public void CheckHealthAsync_WorksInSingleThreadedSyncContext() Assert.False(hangs); } + [Fact] + public void CheckHealthAsync_IndividualPeriodicity_WorksInSingleThreadedSyncContext() + { + // Arrange + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("test", async () => + { + await Task.Delay(10).ConfigureAwait(false); + return HealthCheckResult.Healthy(); + }, period: TimeSpan.FromSeconds(4)); + }); + + var hangs = true; + + // Act + using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) + { + var token = cts.Token; + token.Register(() => throw new OperationCanceledException(token)); + + SingleThreadedSynchronizationContext.Run(() => + { + // Act + service.CheckHealthAsync(token).GetAwaiter().GetResult(); + hangs = false; + }); + } + + // Assert + Assert.False(hangs); + } + [Fact] public async Task CheckHealthAsync_WithFailureStatus() { @@ -624,12 +918,17 @@ public async Task CheckHealthAsync_WithFailureStatus() }); } - private static DefaultHealthCheckService CreateHealthChecksService(Action configure) + private static DefaultHealthCheckService CreateHealthChecksService(Action configure, Action? healthCheckPublisherOptions = default) { var services = new ServiceCollection(); services.AddLogging(); services.AddOptions(); + if (healthCheckPublisherOptions != null) + { + services.Configure(healthCheckPublisherOptions); + } + var builder = services.AddHealthChecks(); if (configure != null) { From f68626612e370f44234907bd1db239f06b735cd9 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sat, 16 Jul 2022 14:04:02 +0200 Subject: [PATCH 15/91] Add groupby comment and simplify ToDictionary --- .../HealthChecks/src/DefaultHealthCheckService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 6d1ae16eb97f..fbf51f1b03c6 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -44,11 +44,13 @@ public DefaultHealthCheckService( _healthReportEntriesQueue = new ConcurrentQueue>(); + // Group healthcheck registrations by period, to build a Dictionary> _periodHealthChecksMap = (from r in _healthCheckServiceOptions.Value.Registrations where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publisher predicate where r.Period != Timeout.InfiniteTimeSpan // Skip HCs with no individual period - group r by r.Period).ToDictionary, TimeSpan, List>(g => g.Key, g => g.ToList()); + group r by r.Period).ToDictionary(g => g.Key, g => g.ToList()); + // Aggregate Timers for HealthCheckRegistration having the same period _healthCheckRegistrationTimers = _periodHealthChecksMap.AsParallel().Select(t => NonCapturingTimer.Create( async (state) => From a91cce397a2fe06a0aec4c383594a360a44911cd Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 17 Jul 2022 16:12:53 +0200 Subject: [PATCH 16/91] Update Entries Dictionary to KV to allow duplicates --- src/HealthChecks/Abstractions/src/HealthReport.cs | 13 +++++++------ .../Abstractions/src/PublicAPI.Unshipped.txt | 3 +++ .../HealthChecks/src/DefaultHealthCheckService.cs | 11 ++++------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthReport.cs b/src/HealthChecks/Abstractions/src/HealthReport.cs index 829067fb3cfa..40a9db16ee4d 100644 --- a/src/HealthChecks/Abstractions/src/HealthReport.cs +++ b/src/HealthChecks/Abstractions/src/HealthReport.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Extensions.Diagnostics.HealthChecks; @@ -14,12 +15,12 @@ public sealed class HealthReport /// /// Create a new from the specified results. /// - /// A containing the results from each health check. + /// A containing the results from each health check. /// A value indicating the time the health check service took to execute. - public HealthReport(IReadOnlyDictionary entries, TimeSpan totalDuration) + public HealthReport(IEnumerable> entries, TimeSpan totalDuration) : this( entries, - CalculateAggregateStatus(entries?.Values ?? throw new ArgumentNullException(nameof(entries))), + CalculateAggregateStatus(entries?.Select(e => e.Value) ?? throw new ArgumentNullException(nameof(entries))), totalDuration) { } @@ -27,10 +28,10 @@ public HealthReport(IReadOnlyDictionary entries, Time /// /// Create a new from the specified results. /// - /// A containing the results from each health check. + /// A containing the results from each health check. /// A representing the aggregate status of all the health checks. /// A value indicating the time the health check service took to execute. - public HealthReport(IReadOnlyDictionary entries, HealthStatus status, TimeSpan totalDuration) + public HealthReport(IEnumerable> entries, HealthStatus status, TimeSpan totalDuration) { Entries = entries; Status = status; @@ -44,7 +45,7 @@ public HealthReport(IReadOnlyDictionary entries, Heal /// The keys in this dictionary map the name of each executed health check to a for the /// result data returned from the corresponding health check. /// - public IReadOnlyDictionary Entries { get; } + public IEnumerable> Entries { get; } /// /// Gets a representing the aggregate status of all the health checks. The value of diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 3314b931cb8a..7574f406ee03 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -3,3 +3,6 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthChec Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? period) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.get -> System.TimeSpan Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Entries.get -> System.Collections.Generic.IEnumerable>! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IEnumerable>! entries, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, System.TimeSpan totalDuration) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IEnumerable>! entries, System.TimeSpan totalDuration) -> void diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index fbf51f1b03c6..e320c10ff4ee 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -74,10 +74,11 @@ public override async Task CheckHealthAsync(Func ByPredicate(r, predicate)).ToList(); + // Filter registrations by default publisher period and predicate + var registrationsDefaultPeriod = _healthCheckServiceOptions.Value.Registrations.Where(r => r.Period == Timeout.InfiniteTimeSpan && ByPredicate(r, predicate)).ToList(); // Run healthchecks with default/publisher period - var healthReportEntriesValues = await RunChecksAsync(registrations, cancellationToken).ConfigureAwait(false); + var healthReportEntriesValues = await RunChecksAsync(registrationsDefaultPeriod, cancellationToken).ConfigureAwait(false); // Collect health report entries from previously run periodic healthchecks within the default period while (_healthReportEntriesQueue.TryDequeue(out var entry)) @@ -85,12 +86,8 @@ public override async Task CheckHealthAsync(Func( - healthReportEntriesValues.ToDictionary(kv => kv.Key, kv => kv.Value), - StringComparer.OrdinalIgnoreCase); - var totalElapsedTime = totalTime.GetElapsedTime(); - var report = new HealthReport(healthReportEntriesDictionary, totalElapsedTime); + var report = new HealthReport(healthReportEntriesValues.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase), totalElapsedTime); Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); return report; From 0fb75e2b73cc60acde811d47a45e839afd79c589 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 17 Jul 2022 16:13:54 +0200 Subject: [PATCH 17/91] Update unit tests for Entries KV list, add delay for collecting entries --- .../test/DefaultHealthCheckServiceTest.cs | 152 +++++++++++++++--- .../HealthCheckPublisherHostedServiceTest.cs | 6 +- 2 files changed, 134 insertions(+), 24 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index d6659d39345c..73e1e366f8e9 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -138,11 +138,12 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes }); // Act + await Task.Delay(TimeSpan.FromSeconds(20)); var results = await service.CheckHealthAsync(); // Assert Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key), + results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), actual => { Assert.Equal("Check1Period3", actual.Key); @@ -212,11 +213,113 @@ public async Task CheckAsync_MixedPublisherIndividualPeriodicity_RunsAllChecksAn }); // Act + await Task.Delay(TimeSpan.FromSeconds(10)); var results = await service.CheckHealthAsync(); // Assert Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key), + results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), + actual => + { + Assert.Equal("Check1PeriodDefault", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check2PeriodDefault", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check3Period3", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check4Period7", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }, + actual => + { + Assert.Equal("Check5Period12", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); + }); + } + + [Fact] + public async Task CheckAsync_MixedPublisherIndividualPeriodicityWithinDefaultPeriod_RunsAllChecksAndAggregatesResultsAsync() + { + const string DataKey = "Foo"; + const string DataValue = "Bar"; + const string HealthyMessage = "Everything is A-OK"; + var healthyCheckTags = new List { "healthy-check-tag" }; + + // Arrange + var data = new Dictionary() + { + { DataKey, DataValue } + }; + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("Check1PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("Check2PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("Check3Period3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(1)); + b.AddAsyncCheck("Check4Period7", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(7)); + b.AddAsyncCheck("Check5Period12", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(11)); + }, + options => + { + options.Period = TimeSpan.FromSeconds(50); + }); + + // Act + await Task.Delay(TimeSpan.FromSeconds(20)); + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), actual => { Assert.Equal("Check1PeriodDefault", actual.Key); @@ -393,10 +496,11 @@ public async Task CheckAsync_IndividualPeriodicity_RunsFilteredChecksThroughPred }); // Act + await Task.Delay(TimeSpan.FromSeconds(20)); var results = await service.CheckHealthAsync(); // Assert - Assert.Collection(results.Entries, + Assert.Collection(results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), actual => { Assert.Equal("PredicateCheck1", actual.Key); @@ -442,7 +546,7 @@ public async Task CheckHealthAsync_SetsRegistrationForEachCheck() // Assert Assert.Collection( - results.Entries, + results.Entries.OrderBy(kvp => kvp.Key), actual => { Assert.Equal("A", actual.Key); @@ -528,6 +632,8 @@ public async Task CheckHealthAsync_IndividualPeriodicity_Cancellation_CanPropaga cancel.Cancel(); + // TODO handle missing cancellation + // Act & Assert await Assert.ThrowsAsync(async () => await task); } @@ -553,13 +659,6 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu Assert.Collection( results.Entries, actual => - { - Assert.Equal("Throws", actual.Key); - Assert.Equal(thrownException.Message, actual.Value.Description); - Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); - Assert.Same(thrownException, actual.Value.Exception); - }, - actual => { Assert.Equal("Faults", actual.Key); Assert.Equal(faultedException.Message, actual.Value.Description); @@ -572,6 +671,13 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu Assert.Null(actual.Value.Description); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); Assert.Null(actual.Value.Exception); + }, + actual => + { + Assert.Equal("Throws", actual.Key); + Assert.Equal(thrownException.Message, actual.Value.Description); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + Assert.Same(thrownException, actual.Value.Exception); }); } @@ -609,7 +715,9 @@ public async Task CheckHealthAsync_SetsUpALoggerScopeForEachCheck() var results = await service.CheckHealthAsync(); // Assert - Assert.Collection(results.Entries, actual => + Assert.Collection( + results.Entries, + actual => { Assert.Equal("TestScope", actual.Key); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); @@ -682,7 +790,7 @@ public async Task CheckHealthAsync_CheckCanDependOnScopedService_per_check() // Assert Assert.Collection( - results.Entries, + results.Entries.OrderBy(kvp => kvp.Key), actual => { Assert.Equal("Test", actual.Key); @@ -712,7 +820,8 @@ public async Task CheckHealthAsync_CheckCanHaveScopedDisposableDependencies() var results = await service.CheckHealthAsync(); // Assert - var healthCheck = (DisposableDependeciesCheck)results.Entries.Single().Value.Data.Single().Value; + var healthCheck = (DisposableDependeciesCheck)results.Entries + .Single().Value.Data.Single().Value; Assert.True(healthCheck.SynchronousDisposable.IsDisposed); Assert.True(healthCheck.AsyncOnlyDisposable.IsAsyncDisposed); @@ -779,7 +888,8 @@ public async Task CheckHealthAsync_ChecksAreRunInParallel() await checkHealthTask; // Assert - Assert.Collection(checkHealthTask.Result.Entries, + Assert.Collection( + checkHealthTask.Result.Entries.OrderBy(kvp => kvp.Key), entry => { Assert.Equal("test1", entry.Key); @@ -900,7 +1010,7 @@ public async Task CheckHealthAsync_WithFailureStatus() // Assert Assert.Collection( - results.Entries, + results.Entries.OrderBy(kvp => kvp.Key), actual => { Assert.Equal("degraded", actual.Key); @@ -924,17 +1034,17 @@ private static DefaultHealthCheckService CreateHealthChecksService(Action(healthCheckPublisherOptions); - } - var builder = services.AddHealthChecks(); if (configure != null) { configure(builder); } + if (healthCheckPublisherOptions != null) + { + services.Configure(healthCheckPublisherOptions); + } + return (DefaultHealthCheckService)services.BuildServiceProvider(validateScopes: true).GetRequiredService(); } diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index b7dcd8e6d42f..8372033e4795 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -215,7 +215,7 @@ public async Task RunAsync_WaitsForCompletion_Single() for (var i = 0; i < publishers.Length; i++) { var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); + Assert.Equal(new[] { "one", "two", }, report.Entries.Select(e => e.Key).OrderBy(k => k)); } } finally @@ -281,7 +281,7 @@ public async Task RunAsync_WaitsForCompletion_Multiple() for (var i = 0; i < publishers.Length; i++) { var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); + Assert.Equal(new[] { "one", "two", }, report.Entries.Select(e => e.Key).OrderBy(k => k)); } } finally @@ -374,7 +374,7 @@ public async Task RunAsync_CanFilterHealthChecks() for (var i = 0; i < publishers.Length; i++) { var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", }, report.Entries.Keys.OrderBy(k => k)); + Assert.Equal(new[] { "one", }, report.Entries.Select(e => e.Key).OrderBy(k => k)); } } finally From 12c640a6e015cf3a9ce0db627d905ff8bafb86ff Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 20 Jul 2022 18:26:19 +0200 Subject: [PATCH 18/91] Revert HealthReport to IReadOnlyDictionary cctor --- src/HealthChecks/Abstractions/src/HealthReport.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthReport.cs b/src/HealthChecks/Abstractions/src/HealthReport.cs index 40a9db16ee4d..829067fb3cfa 100644 --- a/src/HealthChecks/Abstractions/src/HealthReport.cs +++ b/src/HealthChecks/Abstractions/src/HealthReport.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Microsoft.Extensions.Diagnostics.HealthChecks; @@ -15,12 +14,12 @@ public sealed class HealthReport /// /// Create a new from the specified results. /// - /// A containing the results from each health check. + /// A containing the results from each health check. /// A value indicating the time the health check service took to execute. - public HealthReport(IEnumerable> entries, TimeSpan totalDuration) + public HealthReport(IReadOnlyDictionary entries, TimeSpan totalDuration) : this( entries, - CalculateAggregateStatus(entries?.Select(e => e.Value) ?? throw new ArgumentNullException(nameof(entries))), + CalculateAggregateStatus(entries?.Values ?? throw new ArgumentNullException(nameof(entries))), totalDuration) { } @@ -28,10 +27,10 @@ public HealthReport(IEnumerable> entries /// /// Create a new from the specified results. /// - /// A containing the results from each health check. + /// A containing the results from each health check. /// A representing the aggregate status of all the health checks. /// A value indicating the time the health check service took to execute. - public HealthReport(IEnumerable> entries, HealthStatus status, TimeSpan totalDuration) + public HealthReport(IReadOnlyDictionary entries, HealthStatus status, TimeSpan totalDuration) { Entries = entries; Status = status; @@ -45,7 +44,7 @@ public HealthReport(IEnumerable> entries /// The keys in this dictionary map the name of each executed health check to a for the /// result data returned from the corresponding health check. /// - public IEnumerable> Entries { get; } + public IReadOnlyDictionary Entries { get; } /// /// Gets a representing the aggregate status of all the health checks. The value of From 36754ece58b372f68c93e82352cf5d1642e78d68 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 20 Jul 2022 18:38:57 +0200 Subject: [PATCH 19/91] Introduce HealthReportEntryDictionary with dup keys --- .../src/HealthReportEntryDictionary.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs new file mode 100644 index 000000000000..c53eb21ba851 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; +/// +/// Represents a collection of ordered by keys. +/// +public class HealthReportEntryDictionary : IReadOnlyDictionary +{ + private readonly Dictionary> _entries; + + /// + public HealthReportEntry this[string key] => _entries[key].SingleOrDefault(); + + /// + public IEnumerable Keys => _entries.Keys; + + /// + public IEnumerable Values => _entries.SelectMany(e => e.Value).AsEnumerable(); + + /// + public int Count => _entries.Count; + + /// + /// Create a starting from a of + /// + /// An of + /// An to compare keys. + public HealthReportEntryDictionary(ICollection> entries, StringComparer ordinalIgnoreCase = default) + { + _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), ordinalIgnoreCase ?? StringComparer.Ordinal); + } + + /// + public bool ContainsKey(string key) + { + return _entries.ContainsKey(key); ; + } + + /// + public IEnumerator> GetEnumerator() + { + return _entries.SelectMany(kvp => kvp.Value.Select(v => new KeyValuePair(kvp.Key, v))).GetEnumerator(); + } + + /// + public bool TryGetValue(string key, out HealthReportEntry value) + { + foreach (var entry in _entries) + { + if (entry.Key.Equals(key)) + { + value = entry.Value.First(); + return true; + } + } + + value = default; + return false; + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} From 830f204d308b552fd82077cdd952d54de7fec582 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 20 Jul 2022 18:43:01 +0200 Subject: [PATCH 20/91] Add cancellation for individual periodicity HCs, introduce HealthReportEntryDictionary --- .../src/DefaultHealthCheckService.cs | 72 +++++++++++++++---- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index e320c10ff4ee..942f941deca5 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -24,7 +24,8 @@ internal sealed partial class DefaultHealthCheckService : HealthCheckService private readonly ILogger _logger; private readonly ConcurrentQueue> _healthReportEntriesQueue; private readonly IReadOnlyDictionary> _periodHealthChecksMap; - private readonly ICollection _healthCheckRegistrationTimers; + private readonly IDictionary _healthCheckRegistrationTimers; + private CancellationToken _isStopping; public DefaultHealthCheckService( IServiceScopeFactory scopeFactory, @@ -51,23 +52,63 @@ where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publ group r by r.Period).ToDictionary(g => g.Key, g => g.ToList()); // Aggregate Timers for HealthCheckRegistration having the same period - _healthCheckRegistrationTimers = _periodHealthChecksMap.AsParallel().Select(t => + _healthCheckRegistrationTimers = CreateTimers(_periodHealthChecksMap); + } + + // Internal for unit testing + internal IDictionary CreateTimers(IReadOnlyDictionary> periodHealthChecksMap, CancellationToken cancellationToken = default) + { + return _periodHealthChecksMap.Select(m => CreateTimer(m.Key, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); + } + + internal KeyValuePair CreateTimer(TimeSpan period, List registrations, CancellationToken cancellationToken = default) + { + return new KeyValuePair( + period, NonCapturingTimer.Create( - async (state) => + async (state) => + { + var entries = await RunChecksAsync(registrations, IsStopping).ConfigureAwait(false); + foreach (var entry in entries) { - var entries = await RunChecksAsync(t.Value).ConfigureAwait(false); - foreach (var entry in entries) - { - _healthReportEntriesQueue.Enqueue(entry); - } - }, - null, - dueTime: healthCheckPublisherOptions.Value.Delay, - period: t.Key)).ToList(); + _healthReportEntriesQueue.Enqueue(entry); + } + }, + null, + dueTime: _healthCheckPublisherOptions.Value.Delay, + period: period)); + } + + internal void StopTimers() + { + //IsStopping.ThrowIfCancellationRequested(); + foreach (var timerPeriod in _healthCheckRegistrationTimers.Keys) + { + StopTimer(timerPeriod); + } + } + + internal void StopTimer(TimeSpan period) + { + _healthCheckRegistrationTimers[period].Dispose(); + _healthCheckRegistrationTimers[period] = null; + } + + internal CancellationToken IsStopping + { + get { return _isStopping; } + set + { + if (value != default) + { + _isStopping = value; + } + } } public override async Task CheckHealthAsync(Func? predicate, CancellationToken cancellationToken = default) { + IsStopping = cancellationToken; // TODO: if we decide for the current design, where HCs with default periodicity are initiated during CheckHealthAsync, // individual-periodicity HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService) @@ -78,7 +119,7 @@ public override async Task CheckHealthAsync(Func r.Period == Timeout.InfiniteTimeSpan && ByPredicate(r, predicate)).ToList(); // Run healthchecks with default/publisher period - var healthReportEntriesValues = await RunChecksAsync(registrationsDefaultPeriod, cancellationToken).ConfigureAwait(false); + List>? healthReportEntriesValues = await RunChecksAsync(registrationsDefaultPeriod, cancellationToken).ConfigureAwait(false); // Collect health report entries from previously run periodic healthchecks within the default period while (_healthReportEntriesQueue.TryDequeue(out var entry)) @@ -87,7 +128,7 @@ public override async Task CheckHealthAsync(Func kvp.Key, StringComparer.OrdinalIgnoreCase), totalElapsedTime); + var report = new HealthReport(new HealthReportEntryDictionary(healthReportEntriesValues, StringComparer.OrdinalIgnoreCase), totalElapsedTime); Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); return report; @@ -110,7 +151,8 @@ private async Task>> RunChecksAsync index = 0; foreach (var registration in registrations) { - entries.Add(new KeyValuePair(registration.Name, tasks[index++].Result)); + var healthReportEntry = tasks[index++].Result; + entries.Add(new KeyValuePair(registration.Name, healthReportEntry)); } return entries; From 80f1cd66508fad8cedd183f14bd80546f8810650 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 20 Jul 2022 18:45:59 +0200 Subject: [PATCH 21/91] Add TaskCompletionSource for individual periodicity HCs --- .../test/DefaultHealthCheckServiceTest.cs | 345 ++++++------------ 1 file changed, 113 insertions(+), 232 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 73e1e366f8e9..5bce2af0e0ef 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -121,45 +121,82 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes { const string DataKey = "Foo"; const string DataValue = "Bar"; + const string DegradedMessage = "I'm not feeling so good"; + const string UnhealthyMessage = "Halp!"; const string HealthyMessage = "Everything is A-OK"; + var exception = new Exception("Things are pretty bad!"); var healthyCheckTags = new List { "healthy-check-tag" }; + var degradedCheckTags = new List { "degraded-check-tag" }; + var unhealthyCheckTags = new List { "unhealthy-check-tag" }; // Arrange var data = new Dictionary() { { DataKey, DataValue } }; + var healthyCheckInsideCheck = new TaskCompletionSource(); + var degradedCheckInsideCheck = new TaskCompletionSource(); + var unhealthyCheckInsideCheck = new TaskCompletionSource(); var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("Check1Period3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(3)); - b.AddAsyncCheck("Check2Period7", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(7)); - b.AddAsyncCheck("Check3Period12", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(12)); + b.AddAsyncCheck("HealthyCheck", _ => + { + if (!healthyCheckInsideCheck.Task.IsCompleted) + { + healthyCheckInsideCheck.SetResult(null); + } + + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + }, + healthyCheckTags, + period: TimeSpan.FromSeconds(2)); + + b.AddAsyncCheck("DegradedCheck", _ => + { + if (!degradedCheckInsideCheck.Task.IsCompleted) + { + degradedCheckInsideCheck.SetResult(null); + } + + return Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)); + }, + degradedCheckTags, + period: TimeSpan.FromSeconds(3)); + + b.AddAsyncCheck("UnhealthyCheck", _ => + { + if (!unhealthyCheckInsideCheck.Task.IsCompleted) + { + unhealthyCheckInsideCheck.SetResult(null); + } + return Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)); + + }, + unhealthyCheckTags, + period: TimeSpan.FromSeconds(5)); }); // Act - await Task.Delay(TimeSpan.FromSeconds(20)); + await Task.WhenAll(healthyCheckInsideCheck.Task, degradedCheckInsideCheck.Task, unhealthyCheckInsideCheck.Task, Task.Delay(10000)); var results = await service.CheckHealthAsync(); // Assert + // All individual checks to be triggered at least once Assert.Collection( - results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), + results.Entries.OrderBy(kvp => kvp.Key).GroupBy(kvp => kvp.Key).Select(l => l.First()), actual => { - Assert.Equal("Check1Period3", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal("DegradedCheck", actual.Key); + Assert.Equal(DegradedMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Degraded, actual.Value.Status); Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); + Assert.Empty(actual.Value.Data); + Assert.Equal(actual.Value.Tags, degradedCheckTags); }, actual => { - Assert.Equal("Check2Period7", actual.Key); + Assert.Equal("HealthyCheck", actual.Key); Assert.Equal(HealthyMessage, actual.Value.Description); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); Assert.Null(actual.Value.Exception); @@ -172,26 +209,27 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes }, actual => { - Assert.Equal("Check3Period12", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); + Assert.Equal("UnhealthyCheck", actual.Key); + Assert.Equal(UnhealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + Assert.Same(exception, actual.Value.Exception); + Assert.Empty(actual.Value.Data); + Assert.Equal(actual.Value.Tags, unhealthyCheckTags); }); } [Fact] - public async Task CheckAsync_MixedPublisherIndividualPeriodicity_RunsAllChecksAndAggregatesResultsAsync() + public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsAsync() { const string DataKey = "Foo"; const string DataValue = "Bar"; const string HealthyMessage = "Everything is A-OK"; - var healthyCheckTags = new List { "healthy-check-tag" }; + + var check1PeriodDefaultInsideCheck = new TaskCompletionSource(); + var check2PeriodDefaultInsideCheck = new TaskCompletionSource(); + var check3Period2InsideCheck = new TaskCompletionSource(); + var check4Period5InsideCheck = new TaskCompletionSource(); + var check5Period7InsideCheck = new TaskCompletionSource(); // Arrange var data = new Dictionary() @@ -201,125 +239,69 @@ public async Task CheckAsync_MixedPublisherIndividualPeriodicity_RunsAllChecksAn var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("Check1PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("Check2PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("Check3Period3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(3)); - b.AddAsyncCheck("Check4Period7", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(7)); - b.AddAsyncCheck("Check5Period12", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(12)); - }, - options => - { - options.Period = TimeSpan.FromSeconds(2); - }); - - // Act - await Task.Delay(TimeSpan.FromSeconds(10)); - var results = await service.CheckHealthAsync(); - - // Assert - Assert.Collection( - results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), - actual => - { - Assert.Equal("Check1PeriodDefault", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); - }, - actual => - { - Assert.Equal("Check2PeriodDefault", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => + b.AddAsyncCheck("Check1PeriodDefault", _ => { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); + if (!check1PeriodDefaultInsideCheck.Task.IsCompleted) + { + check1PeriodDefaultInsideCheck.SetResult(null); + } + + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); - }, - actual => - { - Assert.Equal("Check3Period3", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => + + b.AddAsyncCheck("Check2PeriodDefault", _ => { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); + if (!check2PeriodDefaultInsideCheck.Task.IsCompleted) + { + check2PeriodDefaultInsideCheck.SetResult(null); + } + + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); - }, - actual => - { - Assert.Equal("Check4Period7", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => + + b.AddAsyncCheck("Check3Period2", _ => { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); - }, - actual => - { - Assert.Equal("Check5Period12", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => + if (!check3Period2InsideCheck.Task.IsCompleted) + { + check3Period2InsideCheck.SetResult(null); + } + + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + }, period: TimeSpan.FromSeconds(2)); + + b.AddAsyncCheck("Check4Period5", _ => { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); - }); - } + if (!check4Period5InsideCheck.Task.IsCompleted) + { + check4Period5InsideCheck.SetResult(null); + } - [Fact] - public async Task CheckAsync_MixedPublisherIndividualPeriodicityWithinDefaultPeriod_RunsAllChecksAndAggregatesResultsAsync() - { - const string DataKey = "Foo"; - const string DataValue = "Bar"; - const string HealthyMessage = "Everything is A-OK"; - var healthyCheckTags = new List { "healthy-check-tag" }; + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + }, period: TimeSpan.FromSeconds(5)); - // Arrange - var data = new Dictionary() - { - { DataKey, DataValue } - }; + b.AddAsyncCheck("Check5Period7", _ => + { + if (!check5Period7InsideCheck.Task.IsCompleted) + { + check5Period7InsideCheck.SetResult(null); + } - var service = CreateHealthChecksService(b => - { - b.AddAsyncCheck("Check1PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("Check2PeriodDefault", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("Check3Period3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(1)); - b.AddAsyncCheck("Check4Period7", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(7)); - b.AddAsyncCheck("Check5Period12", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags, period: TimeSpan.FromSeconds(11)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + }, period: TimeSpan.FromSeconds(5)); }, options => { - options.Period = TimeSpan.FromSeconds(50); + options.Period = TimeSpan.FromSeconds(2); }); // Act - await Task.Delay(TimeSpan.FromSeconds(20)); + await Task.WhenAll(check3Period2InsideCheck.Task, check4Period5InsideCheck.Task, check5Period7InsideCheck.Task, Task.Delay(10000)); var results = await service.CheckHealthAsync(); // Assert + // All individual checks to be triggered at least once Assert.Collection( - results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), + results.Entries.OrderBy(kvp => kvp.Key).GroupBy(kvp => kvp.Key).Select(l => l.First()), actual => { Assert.Equal("Check1PeriodDefault", actual.Key); @@ -331,7 +313,6 @@ public async Task CheckAsync_MixedPublisherIndividualPeriodicityWithinDefaultPer Assert.Equal(DataKey, item.Key); Assert.Equal(DataValue, item.Value); }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); }, actual => { @@ -344,11 +325,10 @@ public async Task CheckAsync_MixedPublisherIndividualPeriodicityWithinDefaultPer Assert.Equal(DataKey, item.Key); Assert.Equal(DataValue, item.Value); }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); }, actual => { - Assert.Equal("Check3Period3", actual.Key); + Assert.Equal("Check3Period2", actual.Key); Assert.Equal(HealthyMessage, actual.Value.Description); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); Assert.Null(actual.Value.Exception); @@ -357,11 +337,10 @@ public async Task CheckAsync_MixedPublisherIndividualPeriodicityWithinDefaultPer Assert.Equal(DataKey, item.Key); Assert.Equal(DataValue, item.Value); }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); }, actual => { - Assert.Equal("Check4Period7", actual.Key); + Assert.Equal("Check4Period5", actual.Key); Assert.Equal(HealthyMessage, actual.Value.Description); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); Assert.Null(actual.Value.Exception); @@ -370,11 +349,10 @@ public async Task CheckAsync_MixedPublisherIndividualPeriodicityWithinDefaultPer Assert.Equal(DataKey, item.Key); Assert.Equal(DataValue, item.Value); }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); }, actual => { - Assert.Equal("Check5Period12", actual.Key); + Assert.Equal("Check5Period7", actual.Key); Assert.Equal(HealthyMessage, actual.Value.Description); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); Assert.Null(actual.Value.Exception); @@ -383,7 +361,6 @@ public async Task CheckAsync_MixedPublisherIndividualPeriodicityWithinDefaultPer Assert.Equal(DataKey, item.Key); Assert.Equal(DataValue, item.Value); }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); }); } @@ -470,63 +447,6 @@ public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync() }); } - [Fact] - public async Task CheckAsync_IndividualPeriodicity_RunsFilteredChecksThroughPredicateAndAggregatesResultsAsync() - { - const string DataKey = "Foo"; - const string DataValue = "Bar"; - const string HealthyMessage = "Everything is A-OK"; - - // Arrange - var data = new Dictionary - { - { DataKey, DataValue } - }; - - // TODO: currently periodic HCs initiated in the cctor use the default predicate instead of the one passed in CheckHealthAsync - var service = CreateHealthChecksService(b => - { - b.AddAsyncCheck("PredicateCheck1", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), period: TimeSpan.FromSeconds(2)); - b.AddAsyncCheck("PredicateCheck2", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), period: TimeSpan.FromSeconds(7)); - b.AddAsyncCheck("Check3", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), period: TimeSpan.FromSeconds(11)); - }, - options => - { - options.Predicate = r => r.Name.Contains("Predicate"); - }); - - // Act - await Task.Delay(TimeSpan.FromSeconds(20)); - var results = await service.CheckHealthAsync(); - - // Assert - Assert.Collection(results.Entries.GroupBy(kvp => kvp.Key).Select(x => x.First()).OrderBy(kvp => kvp.Key), - actual => - { - Assert.Equal("PredicateCheck1", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - }, - actual => - { - Assert.Equal("PredicateCheck2", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - }); - } - [Fact] public async Task CheckHealthAsync_SetsRegistrationForEachCheck() { @@ -599,45 +519,6 @@ public async Task CheckHealthAsync_Cancellation_CanPropagate() await Assert.ThrowsAsync(async () => await task); } - [Fact] - public async Task CheckHealthAsync_IndividualPeriodicity_Cancellation_CanPropagate() - { - // Arrange - var insideCheck = new TaskCompletionSource(); - - var service = CreateHealthChecksService(b => - { - b.AddAsyncCheck("cancels1", async ct => - { - insideCheck.SetResult(null); - - await Task.Delay(10000, ct); - return HealthCheckResult.Unhealthy(); - }, period: TimeSpan.FromSeconds(5)); - - b.AddAsyncCheck("cancels2", async ct => - { - insideCheck.SetResult(null); - - await Task.Delay(10000, ct); - return HealthCheckResult.Unhealthy(); - }, period: TimeSpan.FromSeconds(7)); - }); - - var cancel = new CancellationTokenSource(); - var task = service.CheckHealthAsync(cancel.Token); - - // After this returns we know the check has started - await insideCheck.Task; - - cancel.Cancel(); - - // TODO handle missing cancellation - - // Act & Assert - await Assert.ThrowsAsync(async () => await task); - } - [Fact] public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResultAsync() { @@ -657,7 +538,7 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu // Assert Assert.Collection( - results.Entries, + results.Entries.OrderBy(kvp => kvp.Key), actual => { Assert.Equal("Faults", actual.Key); From e9b5ef760eae1f92997a86a1d755e20edcea8923 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 20 Jul 2022 18:47:00 +0200 Subject: [PATCH 22/91] Add HealthReportEntryDictionary public API --- .../Abstractions/src/PublicAPI.Unshipped.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 7574f406ee03..56cc1b356118 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -6,3 +6,12 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.set Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Entries.get -> System.Collections.Generic.IEnumerable>! Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IEnumerable>! entries, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, System.TimeSpan totalDuration) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IEnumerable>! entries, System.TimeSpan totalDuration) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.ContainsKey(string! key) -> bool +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Count.get -> int +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.GetEnumerator() -> System.Collections.Generic.IEnumerator>! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.HealthReportEntryDictionary(System.Collections.Generic.ICollection>! entries, System.StringComparer! ordinalIgnoreCase = null) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Keys.get -> System.Collections.Generic.IEnumerable! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.this[string! key].get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.TryGetValue(string! key, out Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry value) -> bool +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Values.get -> System.Collections.Generic.IEnumerable! From bcea0211e930215100559c13f6e7d58de0fc43f3 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 20 Jul 2022 18:51:21 +0200 Subject: [PATCH 23/91] Revert HealthCheckPublisherHostedServiceTest --- .../test/HealthCheckPublisherHostedServiceTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 8372033e4795..b7dcd8e6d42f 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -215,7 +215,7 @@ public async Task RunAsync_WaitsForCompletion_Single() for (var i = 0; i < publishers.Length; i++) { var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", "two", }, report.Entries.Select(e => e.Key).OrderBy(k => k)); + Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); } } finally @@ -281,7 +281,7 @@ public async Task RunAsync_WaitsForCompletion_Multiple() for (var i = 0; i < publishers.Length; i++) { var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", "two", }, report.Entries.Select(e => e.Key).OrderBy(k => k)); + Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); } } finally @@ -374,7 +374,7 @@ public async Task RunAsync_CanFilterHealthChecks() for (var i = 0; i < publishers.Length; i++) { var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", }, report.Entries.Select(e => e.Key).OrderBy(k => k)); + Assert.Equal(new[] { "one", }, report.Entries.Keys.OrderBy(k => k)); } } finally From 90fcd4e70dea39fbc0f0c511d0cb25d4fea484e1 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 16:49:28 +0200 Subject: [PATCH 24/91] Add HealthReportEntryDictionary comments, fix string comparison --- .../src/HealthReportEntryDictionary.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs index c53eb21ba851..5f48b9ce32cd 100644 --- a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs +++ b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs @@ -9,15 +9,21 @@ using System.Threading.Tasks; namespace Microsoft.Extensions.Diagnostics.HealthChecks; + /// /// Represents a collection of ordered by keys. /// -public class HealthReportEntryDictionary : IReadOnlyDictionary +public sealed class HealthReportEntryDictionary : IReadOnlyDictionary { + // We make the HealthReportEntryDictionary implements IReadOnlyDictionary, so to have a Dictionary interface to be used by the HealthReport cctor accepting duplicate keys private readonly Dictionary> _entries; - /// - public HealthReportEntry this[string key] => _entries[key].SingleOrDefault(); + /// + /// Returns the first corresponding to the given key + /// + /// + /// The first corresponding to the given key + public HealthReportEntry this[string key] => _entries[key].FirstOrDefault(); /// public IEnumerable Keys => _entries.Keys; @@ -32,10 +38,11 @@ public class HealthReportEntryDictionary : IReadOnlyDictionary starting from a of /// /// An of - /// An to compare keys. - public HealthReportEntryDictionary(ICollection> entries, StringComparer ordinalIgnoreCase = default) + /// An to compare keys. + public HealthReportEntryDictionary(ICollection> entries, StringComparer stringComparer = default) { - _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), ordinalIgnoreCase ?? StringComparer.Ordinal); + stringComparer = stringComparer ?? StringComparer.OrdinalIgnoreCase; + _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), stringComparer); } /// @@ -55,7 +62,7 @@ public bool TryGetValue(string key, out HealthReportEntry value) { foreach (var entry in _entries) { - if (entry.Key.Equals(key)) + if (entry.Key.Equals(key, StringComparison.Ordinal)) { value = entry.Value.First(); return true; From a1274c197d91c4cc49b6f935a1f4fe91d09d0646 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 16:50:17 +0200 Subject: [PATCH 25/91] Add HealthReportEntryDictionary unit tests --- .../test/HealthReportEntryDictionaryTest.cs | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs diff --git a/src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs b/src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs new file mode 100644 index 000000000000..dd3d754c6b22 --- /dev/null +++ b/src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +#nullable enable + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +public class HealthReportEntryDictionaryTest +{ + [Fact] + public void Constructor_GenerateDictionaryWithDuplicates() + { + // Arrange + // Act + var result = new HealthReportEntryDictionary(new List>() + { + new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + }); + + // Assert + Assert.Collection( + result.OrderBy(kvp => kvp.Key), + actual => + { + Assert.Equal("Bar", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); + }, + actual => + { + Assert.Equal("Foo", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); + }, + actual => + { + Assert.Equal("Foo", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); + }, + actual => + { + Assert.Equal("Quack", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); + }, + actual => + { + Assert.Equal("Quick", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); + }, + actual => + { + Assert.Equal("Quick", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); + }, + actual => + { + Assert.Equal("Quick", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); + }); + } + + [Fact] + public void Indexer() + { + // Arrange + var first = new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null)); + var healthReportEntryDictionary = new HealthReportEntryDictionary(new List>() + { + first, + new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + }); + + // Act + var result = healthReportEntryDictionary["Foo"]; + + // Assert + Assert.Equal(result, first.Value); + } + + [Fact] + public void ContainsKey() + { + // Arrange + var healthReportEntryDictionary = new HealthReportEntryDictionary(new List>() + { + new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + }); + + // Act + var result = healthReportEntryDictionary.ContainsKey("Foo"); + + // Assert + Assert.True(result); + } + + [Fact] + public void TryGetValue() + { + // Arrange + var first = new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null)); + var healthReportEntryDictionary = new HealthReportEntryDictionary(new List>() + { + first, + new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), + }); + + // Act + var result = healthReportEntryDictionary.TryGetValue("Foo", out var resultOut); + + // Assert + Assert.True(result); + Assert.Equal(resultOut, first.Value); + } + +} From 29568a228551711cf7da3ba2729db200b568bd3a Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 16:57:13 +0200 Subject: [PATCH 26/91] Add usage comments for Cancellation, predicate --- .../HealthChecks/src/DefaultHealthCheckService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 942f941deca5..3495ec670b97 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -81,7 +81,6 @@ internal KeyValuePair CreateTimer(TimeSpan period, List CheckHealthAsync(Func? predicate, CancellationToken cancellationToken = default) { + // Save the cancellation token, so to propagate cancellation to the individual periodicity HCs IsStopping = cancellationToken; + // TODO: if we decide for the current design, where HCs with default periodicity are initiated during CheckHealthAsync, // individual-periodicity HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService) @@ -269,12 +270,12 @@ private static void ValidateRegistrations(IEnumerable r private bool ByPredicate(HealthCheckRegistration registration, Func? predicate) { + // Default to the HealthCheckPublisher predicate if (predicate == null) { predicate = _healthCheckPublisherOptions.Value.Predicate; } - // Default to the HealthCheckPublisher predicate if (predicate == null) { return true; From 1836caf01d10c6a5120c075068491147c005f013 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 17:03:29 +0200 Subject: [PATCH 27/91] Use compound assignment --- .../Abstractions/src/HealthReportEntryDictionary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs index 5f48b9ce32cd..88585b77c1c6 100644 --- a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs +++ b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs @@ -41,7 +41,7 @@ public sealed class HealthReportEntryDictionary : IReadOnlyDictionaryAn to compare keys. public HealthReportEntryDictionary(ICollection> entries, StringComparer stringComparer = default) { - stringComparer = stringComparer ?? StringComparer.OrdinalIgnoreCase; + stringComparer ??= StringComparer.OrdinalIgnoreCase; _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), stringComparer); } From e47a61e3d78dda6b890fedccfd6c08776d8b47f8 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 17:22:36 +0200 Subject: [PATCH 28/91] Revert HealthChecks/src/PublicAPI.Shipped.txt --- .../HealthChecks/src/PublicAPI.Shipped.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index 6543d9fab8f7..cb2a9b173676 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -22,20 +22,20 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.HealthCheckServ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.HealthCheckServiceOptions() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.Registrations.get -> System.Collections.Generic.ICollection! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! \ No newline at end of file From 35d150012d67cf6331e0c5aad0d55b72d6eb4397 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 17:23:50 +0200 Subject: [PATCH 29/91] Add HealthReportEntryDictionary public API --- src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 56cc1b356118..bf8fb3698a60 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -10,7 +10,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.ContainsKey(string! key) -> bool Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Count.get -> int Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.GetEnumerator() -> System.Collections.Generic.IEnumerator>! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.HealthReportEntryDictionary(System.Collections.Generic.ICollection>! entries, System.StringComparer! ordinalIgnoreCase = null) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.HealthReportEntryDictionary(System.Collections.Generic.ICollection>! entries, System.StringComparer! stringComparer = null) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Keys.get -> System.Collections.Generic.IEnumerable! Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.this[string! key].get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.TryGetValue(string! key, out Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry value) -> bool From 6613e96285e9da7f8e50098f39dac51771f45b2d Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 17:24:45 +0200 Subject: [PATCH 30/91] Update missing APIs HealthChecks/src/PublicAPI.Unshipped.txt --- src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 276e1353eba1..e72a013e8533 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,2 +1,8 @@ #nullable enable +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, System.TimeSpan period, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! \ No newline at end of file From ba51c873b7f042ef181ea88d49485f6bf63cb8aa Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 17:41:58 +0200 Subject: [PATCH 31/91] Fix unused param periodHealthChecksMap --- src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 3495ec670b97..26ec589dfdaf 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -58,7 +58,7 @@ where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publ // Internal for unit testing internal IDictionary CreateTimers(IReadOnlyDictionary> periodHealthChecksMap, CancellationToken cancellationToken = default) { - return _periodHealthChecksMap.Select(m => CreateTimer(m.Key, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); + return periodHealthChecksMap.Select(m => CreateTimer(m.Key, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); } internal KeyValuePair CreateTimer(TimeSpan period, List registrations, CancellationToken cancellationToken = default) From 2ff4e1368406303c349769c3b3057d3a8f8d9eaf Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 17:43:58 +0200 Subject: [PATCH 32/91] Fix unused param cancellationToken --- src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 26ec589dfdaf..20dd755e7102 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -68,7 +68,7 @@ internal KeyValuePair CreateTimer(TimeSpan period, List { - var entries = await RunChecksAsync(registrations, IsStopping).ConfigureAwait(false); + var entries = await RunChecksAsync(registrations, cancellationToken).ConfigureAwait(false); foreach (var entry in entries) { _healthReportEntriesQueue.Enqueue(entry); From 6b0ae5ba06b0e1d6b386f9f0f0dc9fc9cc3ece8b Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 18:02:54 +0200 Subject: [PATCH 33/91] Fix cannot convert null literal to non-nullable reference type --- src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 20dd755e7102..8b28f7fb4484 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -90,7 +90,6 @@ internal void StopTimers() internal void StopTimer(TimeSpan period) { _healthCheckRegistrationTimers[period].Dispose(); - _healthCheckRegistrationTimers[period] = null; } internal CancellationToken IsStopping From 6aea5b5231f59c344ed6b195631f848e8e29a84c Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 18:12:56 +0200 Subject: [PATCH 34/91] Add nullable StringComparer --- .../Abstractions/src/HealthReportEntryDictionary.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs index 88585b77c1c6..51fc7e23bbe6 100644 --- a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs +++ b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs @@ -39,7 +39,7 @@ public sealed class HealthReportEntryDictionary : IReadOnlyDictionary /// An of /// An to compare keys. - public HealthReportEntryDictionary(ICollection> entries, StringComparer stringComparer = default) + public HealthReportEntryDictionary(ICollection> entries, StringComparer? stringComparer = default) { stringComparer ??= StringComparer.OrdinalIgnoreCase; _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), stringComparer); From 0b252d996527135da85a3bc7845be1e001a20246 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 18:13:55 +0200 Subject: [PATCH 35/91] Remove unneeded HealthReport API changes --- src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index bf8fb3698a60..7905fe2a2dc4 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -3,14 +3,11 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthChec Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? period) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.get -> System.TimeSpan Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Entries.get -> System.Collections.Generic.IEnumerable>! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IEnumerable>! entries, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, System.TimeSpan totalDuration) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IEnumerable>! entries, System.TimeSpan totalDuration) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.ContainsKey(string! key) -> bool Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Count.get -> int Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.GetEnumerator() -> System.Collections.Generic.IEnumerator>! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.HealthReportEntryDictionary(System.Collections.Generic.ICollection>! entries, System.StringComparer! stringComparer = null) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.HealthReportEntryDictionary(System.Collections.Generic.ICollection>! entries, System.StringComparer? stringComparer = null) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Keys.get -> System.Collections.Generic.IEnumerable! Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.this[string! key].get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.TryGetValue(string! key, out Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry value) -> bool From b2eacba5a2e03fd985a102fcb78ea9aa6864afe9 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 18:14:43 +0200 Subject: [PATCH 36/91] Refactor for new AddPeriodicCheck API --- .../HealthChecksBuilderAddCheckExtensions.cs | 95 ++++++++++- .../HealthChecksBuilderDelegateExtensions.cs | 157 +++++++++++++++++- .../HealthChecks/src/PublicAPI.Unshipped.txt | 14 +- .../test/DefaultHealthCheckServiceTest.cs | 14 +- 4 files changed, 255 insertions(+), 25 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index fc77cf40854c..466426f37d89 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -33,7 +33,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus, IEnumerable tags) { - return AddCheck(builder, name, instance, failureStatus, tags, default, default); + return AddCheck(builder, name, instance, failureStatus, tags, default); } /// @@ -48,10 +48,50 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + IHealthCheck instance, + HealthStatus? failureStatus = null, + IEnumerable? tags = null, + TimeSpan? timeout = null) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, default)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// An instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. + /// The . + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static IHealthChecksBuilder AddPeriodicCheck( this IHealthChecksBuilder builder, string name, IHealthCheck instance, @@ -103,7 +143,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus, IEnumerable tags) where T : class, IHealthCheck { - return AddCheck(builder, name, failureStatus, tags, default, default); + return AddCheck(builder, name, failureStatus, tags, default); } /// @@ -118,7 +158,6 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual period of the check. /// The . /// /// This method will use to create the health check @@ -128,6 +167,52 @@ public static IHealthChecksBuilder AddCheck( /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus = null, + IEnumerable? tags = null, + TimeSpan? timeout = null) where T : class, IHealthCheck + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout)); + + [UnconditionalSuppressMessage("Trimming", "IL2091", + Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] + static T GetServiceOrCreateInstance(IServiceProvider serviceProvider) => + ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. If a service of type is registered in the dependency injection container + /// with any lifetime it will be used. Otherwise an instance of type will be constructed with + /// access to services from the dependency injection container. + /// + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static IHealthChecksBuilder AddPeriodicCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus = null, @@ -327,7 +412,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// This method will use to create the health check /// instance when needed. Additional arguments can be provided to the constructor via . /// - public static IHealthChecksBuilder AddTypeActivatedCheck< + public static IHealthChecksBuilder AddTypeActivatedPeriodicCheck< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 2deb60113319..f82d8d0634be 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -41,10 +41,46 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable? tags = null, + TimeSpan? timeout = default) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. + /// The . + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static IHealthChecksBuilder AddPeriodicCheck( this IHealthChecksBuilder builder, string name, Func check, @@ -86,7 +122,43 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags) { - return AddCheck(builder, name, check, tags, default, default); + return AddCheck(builder, name, check, tags, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable? tags = null, + TimeSpan? timeout = default) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -100,7 +172,7 @@ public static IHealthChecksBuilder AddCheck( /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddCheck( + public static IHealthChecksBuilder AddPeriodicCheck( this IHealthChecksBuilder builder, string name, Func check, @@ -153,10 +225,46 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable? tags = null, + TimeSpan? timeout = default) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => check()); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. + /// The . + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static IHealthChecksBuilder AddAsyncPeriodicCheck( this IHealthChecksBuilder builder, string name, Func> check, @@ -198,7 +306,7 @@ public static IHealthChecksBuilder AddAsyncCheck( Func> check, IEnumerable tags) { - return AddAsyncCheck(builder, name, check, tags, default, default); + return AddAsyncCheck(builder, name, check, tags, default); } /// @@ -209,11 +317,48 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable? tags = null, + TimeSpan? timeout = default) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => check(ct)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// An optional representing the individual period of the check. + + /// The . + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] + public static IHealthChecksBuilder AddAsyncPeriodicCheck( this IHealthChecksBuilder builder, string name, Func> check, diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index e72a013e8533..17cba5bd1bee 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1,8 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, System.TimeSpan period, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! \ No newline at end of file +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, System.TimeSpan period, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 5bce2af0e0ef..38871a53a440 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -140,7 +140,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => + b.AddAsyncPeriodicCheck("HealthyCheck", _ => { if (!healthyCheckInsideCheck.Task.IsCompleted) { @@ -152,7 +152,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes healthyCheckTags, period: TimeSpan.FromSeconds(2)); - b.AddAsyncCheck("DegradedCheck", _ => + b.AddAsyncPeriodicCheck("DegradedCheck", _ => { if (!degradedCheckInsideCheck.Task.IsCompleted) { @@ -164,7 +164,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes degradedCheckTags, period: TimeSpan.FromSeconds(3)); - b.AddAsyncCheck("UnhealthyCheck", _ => + b.AddAsyncPeriodicCheck("UnhealthyCheck", _ => { if (!unhealthyCheckInsideCheck.Task.IsCompleted) { @@ -259,7 +259,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }); - b.AddAsyncCheck("Check3Period2", _ => + b.AddAsyncPeriodicCheck("Check3Period2", _ => { if (!check3Period2InsideCheck.Task.IsCompleted) { @@ -269,7 +269,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }, period: TimeSpan.FromSeconds(2)); - b.AddAsyncCheck("Check4Period5", _ => + b.AddAsyncPeriodicCheck("Check4Period5", _ => { if (!check4Period5InsideCheck.Task.IsCompleted) { @@ -279,7 +279,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }, period: TimeSpan.FromSeconds(5)); - b.AddAsyncCheck("Check5Period7", _ => + b.AddAsyncPeriodicCheck("Check5Period7", _ => { if (!check5Period7InsideCheck.Task.IsCompleted) { @@ -848,7 +848,7 @@ public void CheckHealthAsync_IndividualPeriodicity_WorksInSingleThreadedSyncCont // Arrange var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("test", async () => + b.AddAsyncPeriodicCheck("test", async () => { await Task.Delay(10).ConfigureAwait(false); return HealthCheckResult.Healthy(); From 6e496343589e47b850a89bdf0fd4bddae8ba5feb Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 18:17:26 +0200 Subject: [PATCH 37/91] Remove unneeded AddHealthChecks API shipped change --- src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index cb2a9b173676..c01174ef039e 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -38,4 +38,3 @@ static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExten static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! \ No newline at end of file From f572b9cccda1ab9f04c5ec7456da54f75b684363 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 21 Jul 2022 18:20:42 +0200 Subject: [PATCH 38/91] Revert remove unneeded AddHealthChecks API shipped change --- src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index c01174ef039e..a6dae5afb4a7 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -38,3 +38,4 @@ static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExten static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From 52422ff5d4ea7805cb8fd0121e00409abdbde230 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 24 Jul 2022 10:11:04 +0200 Subject: [PATCH 39/91] Mark HealthReportEntryDictionary as internal --- .../src/HealthReportEntryDictionary.cs | 2 +- .../src/HealthReportEntryDictionary.cs | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs index 51fc7e23bbe6..6ce6a9084fe4 100644 --- a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs +++ b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs @@ -13,7 +13,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// /// Represents a collection of ordered by keys. /// -public sealed class HealthReportEntryDictionary : IReadOnlyDictionary +internal sealed class HealthReportEntryDictionary : IReadOnlyDictionary { // We make the HealthReportEntryDictionary implements IReadOnlyDictionary, so to have a Dictionary interface to be used by the HealthReport cctor accepting duplicate keys private readonly Dictionary> _entries; diff --git a/src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs b/src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs new file mode 100644 index 000000000000..6ce6a9084fe4 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represents a collection of ordered by keys. +/// +internal sealed class HealthReportEntryDictionary : IReadOnlyDictionary +{ + // We make the HealthReportEntryDictionary implements IReadOnlyDictionary, so to have a Dictionary interface to be used by the HealthReport cctor accepting duplicate keys + private readonly Dictionary> _entries; + + /// + /// Returns the first corresponding to the given key + /// + /// + /// The first corresponding to the given key + public HealthReportEntry this[string key] => _entries[key].FirstOrDefault(); + + /// + public IEnumerable Keys => _entries.Keys; + + /// + public IEnumerable Values => _entries.SelectMany(e => e.Value).AsEnumerable(); + + /// + public int Count => _entries.Count; + + /// + /// Create a starting from a of + /// + /// An of + /// An to compare keys. + public HealthReportEntryDictionary(ICollection> entries, StringComparer? stringComparer = default) + { + stringComparer ??= StringComparer.OrdinalIgnoreCase; + _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), stringComparer); + } + + /// + public bool ContainsKey(string key) + { + return _entries.ContainsKey(key); ; + } + + /// + public IEnumerator> GetEnumerator() + { + return _entries.SelectMany(kvp => kvp.Value.Select(v => new KeyValuePair(kvp.Key, v))).GetEnumerator(); + } + + /// + public bool TryGetValue(string key, out HealthReportEntry value) + { + foreach (var entry in _entries) + { + if (entry.Key.Equals(key, StringComparison.Ordinal)) + { + value = entry.Value.First(); + return true; + } + } + + value = default; + return false; + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} From 4993cda1fe340bbdacacc854b3d4d6898e061fa1 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 24 Jul 2022 10:12:10 +0200 Subject: [PATCH 40/91] Remove unneeded fields, cancellation for non-default HCs --- .../src/DefaultHealthCheckService.cs | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 8b28f7fb4484..88e023650a34 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -23,9 +23,7 @@ internal sealed partial class DefaultHealthCheckService : HealthCheckService private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly ConcurrentQueue> _healthReportEntriesQueue; - private readonly IReadOnlyDictionary> _periodHealthChecksMap; - private readonly IDictionary _healthCheckRegistrationTimers; - private CancellationToken _isStopping; + private readonly Dictionary> _periodHealthChecksMap; public DefaultHealthCheckService( IServiceScopeFactory scopeFactory, @@ -52,16 +50,16 @@ where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publ group r by r.Period).ToDictionary(g => g.Key, g => g.ToList()); // Aggregate Timers for HealthCheckRegistration having the same period - _healthCheckRegistrationTimers = CreateTimers(_periodHealthChecksMap); + _ = CreateTimers(_periodHealthChecksMap); } - // Internal for unit testing - internal IDictionary CreateTimers(IReadOnlyDictionary> periodHealthChecksMap, CancellationToken cancellationToken = default) + + private Dictionary CreateTimers(IReadOnlyDictionary> periodHealthChecksMap, CancellationToken cancellationToken = default) { return periodHealthChecksMap.Select(m => CreateTimer(m.Key, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); } - internal KeyValuePair CreateTimer(TimeSpan period, List registrations, CancellationToken cancellationToken = default) + private KeyValuePair CreateTimer(TimeSpan period, List registrations, CancellationToken cancellationToken = default) { return new KeyValuePair( period, @@ -79,38 +77,10 @@ internal KeyValuePair CreateTimer(TimeSpan period, List CheckHealthAsync(Func? predicate, CancellationToken cancellationToken = default) { - // Save the cancellation token, so to propagate cancellation to the individual periodicity HCs - IsStopping = cancellationToken; - - // TODO: if we decide for the current design, where HCs with default periodicity are initiated during CheckHealthAsync, - // individual-periodicity HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService) + // In the current design, where HCs with default periodicity are initiated during CheckHealthAsync, + // individual-periodicity HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService instead) var totalTime = ValueStopwatch.StartNew(); Log.HealthCheckProcessingBegin(_logger); From 80f3f48510eb5ef873c99bb5a794abfe31f646a2 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 25 Jul 2022 15:04:59 +0200 Subject: [PATCH 41/91] Introduce delay per HC, set default values for period and delay --- .../src/HealthCheckRegistration.cs | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index efcee48ae0a9..77ce51a7580d 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -25,6 +25,7 @@ public sealed class HealthCheckRegistration private Func _factory; private string _name; private TimeSpan _timeout; + private TimeSpan _delay; private TimeSpan _period; /// @@ -38,7 +39,7 @@ public sealed class HealthCheckRegistration /// /// A list of tags that can be used for filtering health checks. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, instance, failureStatus, tags, default, default) + : this(name, instance, failureStatus, tags, default, default, default) { } @@ -54,7 +55,7 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, instance, failureStatus, tags, timeout, default) + : this(name, instance, failureStatus, tags, timeout, default, default) { } @@ -69,8 +70,9 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? period) + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? delay, TimeSpan? period) { if (name == null) { @@ -87,6 +89,11 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? throw new ArgumentOutOfRangeException(nameof(timeout)); } + if (delay < TimeSpan.Zero || delay == System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(delay)); + } + if (period < TimeSpan.FromSeconds(1) && period != System.Threading.Timeout.InfiniteTimeSpan) { throw new ArgumentOutOfRangeException(nameof(period)); @@ -97,7 +104,8 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = (_) => instance; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Period = period ?? System.Threading.Timeout.InfiniteTimeSpan; + _delay = delay ?? System.Threading.Timeout.InfiniteTimeSpan; // Use InfiniteTimeSpan as default delay for non-default HCs + _period = period ?? default; // Use TimeSpan.Zero as default period for non-default HCs } /// @@ -115,7 +123,7 @@ public HealthCheckRegistration( Func factory, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, factory, failureStatus, tags, default, default) + : this(name, factory, failureStatus, tags, default, default, default) { } @@ -136,7 +144,7 @@ public HealthCheckRegistration( HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, factory, failureStatus, tags, timeout, default) + : this(name, factory, failureStatus, tags, timeout, default, default) { } @@ -151,6 +159,7 @@ public HealthCheckRegistration( /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. public HealthCheckRegistration( string name, @@ -158,6 +167,7 @@ public HealthCheckRegistration( HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, + TimeSpan? delay, TimeSpan? period) { if (name == null) @@ -175,6 +185,11 @@ public HealthCheckRegistration( throw new ArgumentOutOfRangeException(nameof(timeout)); } + if (delay < TimeSpan.Zero || delay == System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(delay)); + } + if (period <= TimeSpan.Zero && period != System.Threading.Timeout.InfiniteTimeSpan) { throw new ArgumentOutOfRangeException(nameof(period)); @@ -185,7 +200,8 @@ public HealthCheckRegistration( Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = factory; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Period = period ?? System.Threading.Timeout.InfiniteTimeSpan; + _delay = delay ?? System.Threading.Timeout.InfiniteTimeSpan; // Use InfiniteTimeSpan as default delay for non-default HCs + _period = period ?? default; // Use TimeSpan.Zero as default period for non-default HCs } /// @@ -227,6 +243,26 @@ public TimeSpan Timeout } } + /// + /// Gets or sets the initial individual delay applied to the + /// individual HealthCheck after the application starts before executing. + /// The delay is applied once at startup, and does + /// not apply to subsequent iterations. The default value is 5 seconds. + /// + public TimeSpan Delay + { + get => _delay; + set + { + if (value < TimeSpan.Zero || value == System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentException($"The {nameof(Delay)} must not be infinite.", nameof(value)); + } + + _delay = value; + } + } + /// /// Gets or sets the individual period used for the check. /// From a152a7277459355ca20758bbfdce2952d02ef7ab Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 25 Jul 2022 15:06:15 +0200 Subject: [PATCH 42/91] Aggregate timers per delay and period, override default values --- .../src/DefaultHealthCheckService.cs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 88e023650a34..849bb04e830a 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -23,7 +23,7 @@ internal sealed partial class DefaultHealthCheckService : HealthCheckService private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly ConcurrentQueue> _healthReportEntriesQueue; - private readonly Dictionary> _periodHealthChecksMap; + private readonly Dictionary<(TimeSpan Delay, TimeSpan Period), List> _individualHealthChecksMap; public DefaultHealthCheckService( IServiceScopeFactory scopeFactory, @@ -43,23 +43,24 @@ public DefaultHealthCheckService( _healthReportEntriesQueue = new ConcurrentQueue>(); - // Group healthcheck registrations by period, to build a Dictionary> - _periodHealthChecksMap = (from r in _healthCheckServiceOptions.Value.Registrations - where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publisher predicate - where r.Period != Timeout.InfiniteTimeSpan // Skip HCs with no individual period - group r by r.Period).ToDictionary(g => g.Key, g => g.ToList()); + // Group healthcheck registrations by delay and period, to build a Dictionary<(TimeSpan, TimeSpan), List> + _individualHealthChecksMap = (from r in _healthCheckServiceOptions.Value.Registrations + where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publisher predicate + where !IsDefaultHealthCheck(r) // Skip default HCs initiated with publisher + group r by (r.Delay, r.Period)).ToDictionary(g => g.Key, g => g.ToList()); // Aggregate Timers for HealthCheckRegistration having the same period - _ = CreateTimers(_periodHealthChecksMap); + _ = CreateTimers(_individualHealthChecksMap); } + private static bool IsDefaultHealthCheck(HealthCheckRegistration registration) => registration.Delay == Timeout.InfiniteTimeSpan && registration.Period == default; - private Dictionary CreateTimers(IReadOnlyDictionary> periodHealthChecksMap, CancellationToken cancellationToken = default) + private Dictionary CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period), List> periodHealthChecksMap, CancellationToken cancellationToken = default) { - return periodHealthChecksMap.Select(m => CreateTimer(m.Key, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); + return periodHealthChecksMap.Select(m => CreateTimer(m.Key.Delay, m.Key.Period, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); } - private KeyValuePair CreateTimer(TimeSpan period, List registrations, CancellationToken cancellationToken = default) + private KeyValuePair CreateTimer(TimeSpan delay, TimeSpan period, List registrations, CancellationToken cancellationToken = default) { return new KeyValuePair( period, @@ -73,23 +74,24 @@ private KeyValuePair CreateTimer(TimeSpan period, List CheckHealthAsync(Func? predicate, CancellationToken cancellationToken = default) { // In the current design, where HCs with default periodicity are initiated during CheckHealthAsync, - // individual-periodicity HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService instead) + // individual HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService instead) var totalTime = ValueStopwatch.StartNew(); Log.HealthCheckProcessingBegin(_logger); - // Filter registrations by default publisher period and predicate - var registrationsDefaultPeriod = _healthCheckServiceOptions.Value.Registrations.Where(r => r.Period == Timeout.InfiniteTimeSpan && ByPredicate(r, predicate)).ToList(); + // Filter registrations by default HC and applied predicate + var registrationsDefaultPeriod = _healthCheckServiceOptions.Value.Registrations.Where(r => IsDefaultHealthCheck(r) && ByPredicate(r, predicate)).ToList(); - // Run healthchecks with default/publisher period - List>? healthReportEntriesValues = await RunChecksAsync(registrationsDefaultPeriod, cancellationToken).ConfigureAwait(false); + // Run healthchecks with default same delay and period as publisher + var healthReportEntriesValues = await RunChecksAsync(registrationsDefaultPeriod, cancellationToken).ConfigureAwait(false); // Collect health report entries from previously run periodic healthchecks within the default period while (_healthReportEntriesQueue.TryDequeue(out var entry)) From 6d4b65d0eb2ff289887533d1f544a5e14e201079 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 25 Jul 2022 15:19:15 +0200 Subject: [PATCH 43/91] Update API contract for individual HC --- .../Abstractions/src/PublicAPI.Unshipped.txt | 15 +++------- .../HealthChecksBuilderAddCheckExtensions.cs | 20 ++++++++----- .../HealthChecksBuilderDelegateExtensions.cs | 28 ++++++++++++------- .../HealthChecks/src/PublicAPI.Unshipped.txt | 14 +++++----- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 7905fe2a2dc4..be30a81b7a9b 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1,14 +1,7 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? period) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? period) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.get -> System.TimeSpan Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.ContainsKey(string! key) -> bool -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Count.get -> int -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.GetEnumerator() -> System.Collections.Generic.IEnumerator>! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.HealthReportEntryDictionary(System.Collections.Generic.ICollection>! entries, System.StringComparer? stringComparer = null) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Keys.get -> System.Collections.Generic.IEnumerable! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.this[string! key].get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.TryGetValue(string! key, out Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry value) -> bool -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntryDictionary.Values.get -> System.Collections.Generic.IEnumerable! diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 466426f37d89..3c50260d1c12 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -73,7 +73,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, default)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, default, default)); } /// @@ -88,16 +88,18 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddPeriodicCheck( + public static IHealthChecksBuilder AddIndividualCheck( this IHealthChecksBuilder builder, string name, IHealthCheck instance, HealthStatus? failureStatus = null, IEnumerable? tags = null, TimeSpan? timeout = null, + TimeSpan? delay = null, TimeSpan? period = null) { if (builder == null) @@ -115,7 +117,7 @@ public static IHealthChecksBuilder AddPeriodicCheck( throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, delay, period)); } /// @@ -203,6 +205,7 @@ static T GetServiceOrCreateInstance(IServiceProvider serviceProvider) => /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. /// The . /// @@ -212,12 +215,13 @@ static T GetServiceOrCreateInstance(IServiceProvider serviceProvider) => /// access to services from the dependency injection container. /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddPeriodicCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( + public static IHealthChecksBuilder AddIndividualCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus = null, IEnumerable? tags = null, TimeSpan? timeout = null, + TimeSpan? delay = null, TimeSpan? period = null) where T : class, IHealthCheck { if (builder == null) @@ -230,7 +234,7 @@ static T GetServiceOrCreateInstance(IServiceProvider serviceProvider) => throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, period)); + return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, delay, period)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] @@ -406,19 +410,21 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. /// A representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. /// The . /// /// This method will use to create the health check /// instance when needed. Additional arguments can be provided to the constructor via . /// - public static IHealthChecksBuilder AddTypeActivatedPeriodicCheck< + public static IHealthChecksBuilder AddTypeActivatedIndividualCheck< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, IEnumerable tags, TimeSpan timeout, + TimeSpan delay, TimeSpan period, params object[] args) where T : class, IHealthCheck { @@ -432,7 +438,7 @@ public static IHealthChecksBuilder AddTypeActivatedPeriodicCheck< throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, period)); + return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, delay, period)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index f82d8d0634be..71cf25196fde 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -66,7 +66,7 @@ public static IHealthChecksBuilder AddCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default, default)); } /// @@ -77,15 +77,17 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddPeriodicCheck( + public static IHealthChecksBuilder AddIndividualCheck( this IHealthChecksBuilder builder, string name, Func check, IEnumerable? tags = null, TimeSpan? timeout = default, + TimeSpan? delay = default, TimeSpan? period = default) { if (builder == null) @@ -104,7 +106,7 @@ public static IHealthChecksBuilder AddPeriodicCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); } /// @@ -169,15 +171,17 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddPeriodicCheck( + public static IHealthChecksBuilder AddIndividualCheck( this IHealthChecksBuilder builder, string name, Func check, IEnumerable? tags = null, TimeSpan? timeout = default, + TimeSpan? delay = default, TimeSpan? period = default) { if (builder == null) @@ -196,7 +200,7 @@ public static IHealthChecksBuilder AddPeriodicCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); } /// @@ -250,7 +254,7 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default, default)); } /// @@ -261,15 +265,17 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddAsyncPeriodicCheck( + public static IHealthChecksBuilder AddAsyncIndividualCheck( this IHealthChecksBuilder builder, string name, Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, + TimeSpan? delay = default, TimeSpan? period = default) { if (builder == null) @@ -288,7 +294,7 @@ public static IHealthChecksBuilder AddAsyncPeriodicCheck( } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); } /// @@ -354,16 +360,18 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddAsyncPeriodicCheck( + public static IHealthChecksBuilder AddAsyncIndividualCheck( this IHealthChecksBuilder builder, string name, Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, + TimeSpan? delay = default, TimeSpan? period = default) { if (builder == null) @@ -382,6 +390,6 @@ public static IHealthChecksBuilder AddAsyncPeriodicCheck( } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); } } diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 17cba5bd1bee..3a1600f2dfd9 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1,8 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, System.TimeSpan period, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddPeriodicCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, System.TimeSpan delay, System.TimeSpan period, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From b2b39603cd5b64615163390645569b927e22ef55 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 25 Jul 2022 15:21:04 +0200 Subject: [PATCH 44/91] Update unit tests for individual HCs, increase Task.Delay to include HC delay --- .../test/DefaultHealthCheckServiceTest.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 38871a53a440..e36c184e925b 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -140,7 +140,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes var service = CreateHealthChecksService(b => { - b.AddAsyncPeriodicCheck("HealthyCheck", _ => + b.AddAsyncIndividualCheck("HealthyCheck", _ => { if (!healthyCheckInsideCheck.Task.IsCompleted) { @@ -152,7 +152,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes healthyCheckTags, period: TimeSpan.FromSeconds(2)); - b.AddAsyncPeriodicCheck("DegradedCheck", _ => + b.AddAsyncIndividualCheck("DegradedCheck", _ => { if (!degradedCheckInsideCheck.Task.IsCompleted) { @@ -164,7 +164,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes degradedCheckTags, period: TimeSpan.FromSeconds(3)); - b.AddAsyncPeriodicCheck("UnhealthyCheck", _ => + b.AddAsyncIndividualCheck("UnhealthyCheck", _ => { if (!unhealthyCheckInsideCheck.Task.IsCompleted) { @@ -178,7 +178,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes }); // Act - await Task.WhenAll(healthyCheckInsideCheck.Task, degradedCheckInsideCheck.Task, unhealthyCheckInsideCheck.Task, Task.Delay(10000)); + await Task.WhenAll(healthyCheckInsideCheck.Task, degradedCheckInsideCheck.Task, unhealthyCheckInsideCheck.Task, Task.Delay(15000)); var results = await service.CheckHealthAsync(); // Assert @@ -259,7 +259,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }); - b.AddAsyncPeriodicCheck("Check3Period2", _ => + b.AddAsyncIndividualCheck("Check3Period2", _ => { if (!check3Period2InsideCheck.Task.IsCompleted) { @@ -269,7 +269,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }, period: TimeSpan.FromSeconds(2)); - b.AddAsyncPeriodicCheck("Check4Period5", _ => + b.AddAsyncIndividualCheck("Check4Period5", _ => { if (!check4Period5InsideCheck.Task.IsCompleted) { @@ -279,7 +279,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }, period: TimeSpan.FromSeconds(5)); - b.AddAsyncPeriodicCheck("Check5Period7", _ => + b.AddAsyncIndividualCheck("Check5Period7", _ => { if (!check5Period7InsideCheck.Task.IsCompleted) { @@ -287,7 +287,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA } return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); - }, period: TimeSpan.FromSeconds(5)); + }, period: TimeSpan.FromSeconds(7)); }, options => { @@ -295,7 +295,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA }); // Act - await Task.WhenAll(check3Period2InsideCheck.Task, check4Period5InsideCheck.Task, check5Period7InsideCheck.Task, Task.Delay(10000)); + await Task.WhenAll(check3Period2InsideCheck.Task, check4Period5InsideCheck.Task, check5Period7InsideCheck.Task, Task.Delay(15000)); var results = await service.CheckHealthAsync(); // Assert @@ -848,7 +848,7 @@ public void CheckHealthAsync_IndividualPeriodicity_WorksInSingleThreadedSyncCont // Arrange var service = CreateHealthChecksService(b => { - b.AddAsyncPeriodicCheck("test", async () => + b.AddAsyncIndividualCheck("test", async () => { await Task.Delay(10).ConfigureAwait(false); return HealthCheckResult.Healthy(); From a98423907ec0ed612f3ea2b848831d0f6aaf7af8 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 25 Jul 2022 15:42:00 +0200 Subject: [PATCH 45/91] Remove duplicate HealthReportEntryDictionary --- .../src/HealthReportEntryDictionary.cs | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs b/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs deleted file mode 100644 index 6ce6a9084fe4..000000000000 --- a/src/HealthChecks/Abstractions/src/HealthReportEntryDictionary.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represents a collection of ordered by keys. -/// -internal sealed class HealthReportEntryDictionary : IReadOnlyDictionary -{ - // We make the HealthReportEntryDictionary implements IReadOnlyDictionary, so to have a Dictionary interface to be used by the HealthReport cctor accepting duplicate keys - private readonly Dictionary> _entries; - - /// - /// Returns the first corresponding to the given key - /// - /// - /// The first corresponding to the given key - public HealthReportEntry this[string key] => _entries[key].FirstOrDefault(); - - /// - public IEnumerable Keys => _entries.Keys; - - /// - public IEnumerable Values => _entries.SelectMany(e => e.Value).AsEnumerable(); - - /// - public int Count => _entries.Count; - - /// - /// Create a starting from a of - /// - /// An of - /// An to compare keys. - public HealthReportEntryDictionary(ICollection> entries, StringComparer? stringComparer = default) - { - stringComparer ??= StringComparer.OrdinalIgnoreCase; - _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), stringComparer); - } - - /// - public bool ContainsKey(string key) - { - return _entries.ContainsKey(key); ; - } - - /// - public IEnumerator> GetEnumerator() - { - return _entries.SelectMany(kvp => kvp.Value.Select(v => new KeyValuePair(kvp.Key, v))).GetEnumerator(); - } - - /// - public bool TryGetValue(string key, out HealthReportEntry value) - { - foreach (var entry in _entries) - { - if (entry.Key.Equals(key, StringComparison.Ordinal)) - { - value = entry.Value.First(); - return true; - } - } - - value = default; - return false; - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } -} From b8cf31df6fa4abf609edb685e3fbef7259aff16d Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 26 Jul 2022 12:12:09 +0200 Subject: [PATCH 46/91] Update comment to include delay aggregation --- src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 849bb04e830a..100bfed23b40 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -49,7 +49,7 @@ where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publ where !IsDefaultHealthCheck(r) // Skip default HCs initiated with publisher group r by (r.Delay, r.Period)).ToDictionary(g => g.Key, g => g.ToList()); - // Aggregate Timers for HealthCheckRegistration having the same period + // Aggregate Timers for HealthCheckRegistration having the same delay and period _ = CreateTimers(_individualHealthChecksMap); } From 639f9bc9b57e694b6ac5da2c4a7533203a98b115 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 26 Jul 2022 16:50:13 +0200 Subject: [PATCH 47/91] Fix timers aggregation per delay, period --- .../HealthChecks/src/DefaultHealthCheckService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 100bfed23b40..f0a9aac15ca3 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -55,15 +55,15 @@ where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publ private static bool IsDefaultHealthCheck(HealthCheckRegistration registration) => registration.Delay == Timeout.InfiniteTimeSpan && registration.Period == default; - private Dictionary CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period), List> periodHealthChecksMap, CancellationToken cancellationToken = default) + private Dictionary<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period), List> periodHealthChecksMap, CancellationToken cancellationToken = default) { return periodHealthChecksMap.Select(m => CreateTimer(m.Key.Delay, m.Key.Period, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); } - private KeyValuePair CreateTimer(TimeSpan delay, TimeSpan period, List registrations, CancellationToken cancellationToken = default) + private KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimer(TimeSpan delay, TimeSpan period, List registrations, CancellationToken cancellationToken = default) { - return new KeyValuePair( - period, + return new KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer>( + (delay, period), NonCapturingTimer.Create( async (state) => { From be22fccf95c5a4dc878d4468a8228e3a527082fc Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 26 Jul 2022 23:51:42 +0200 Subject: [PATCH 48/91] Fix apply predicate at each HealthCheck run --- .../HealthChecks/src/DefaultHealthCheckService.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index f0a9aac15ca3..20836c61e4af 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -45,7 +45,6 @@ public DefaultHealthCheckService( // Group healthcheck registrations by delay and period, to build a Dictionary<(TimeSpan, TimeSpan), List> _individualHealthChecksMap = (from r in _healthCheckServiceOptions.Value.Registrations - where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publisher predicate where !IsDefaultHealthCheck(r) // Skip default HCs initiated with publisher group r by (r.Delay, r.Period)).ToDictionary(g => g.Key, g => g.ToList()); @@ -67,7 +66,8 @@ where ByPredicate(r, _healthCheckPublisherOptions.Value.Predicate) // Apply publ NonCapturingTimer.Create( async (state) => { - var entries = await RunChecksAsync(registrations, cancellationToken).ConfigureAwait(false); + var checksToRun = registrations.Where(r => ByPredicate(r)).ToList(); // Apply publisher predicate + var entries = await RunChecksAsync(checksToRun, cancellationToken).ConfigureAwait(false); foreach (var entry in entries) { _healthReportEntriesQueue.Enqueue(entry); @@ -239,15 +239,15 @@ private static void ValidateRegistrations(IEnumerable r } } - private bool ByPredicate(HealthCheckRegistration registration, Func? predicate) + private bool ByPredicate(HealthCheckRegistration registration, Func? predicate = default) { // Default to the HealthCheckPublisher predicate - if (predicate == null) + if (predicate == default) { predicate = _healthCheckPublisherOptions.Value.Predicate; } - if (predicate == null) + if (predicate == default) { return true; } From 3e7cb5a8f3e4d0c6a7f7e4d4e16f72b5c7b7872d Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 3 Aug 2022 17:18:12 +0100 Subject: [PATCH 49/91] Introduce HealthCheckOptions --- .../Abstractions/src/HealthCheckOptions.cs | 37 ++++ .../src/HealthCheckRegistration.cs | 84 ++------ .../Abstractions/src/PublicAPI.Unshipped.txt | 13 +- .../src/DefaultHealthCheckService.cs | 8 +- .../HealthChecksBuilderAddCheckExtensions.cs | 112 +---------- .../HealthChecksBuilderDelegateExtensions.cs | 179 ++---------------- .../HealthChecks/src/PublicAPI.Shipped.txt | 12 +- .../HealthChecks/src/PublicAPI.Unshipped.txt | 8 +- .../test/DefaultHealthCheckServiceTest.cs | 29 +-- 9 files changed, 105 insertions(+), 377 deletions(-) create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckOptions.cs diff --git a/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs b/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs new file mode 100644 index 000000000000..e25f6255b283 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Represent the individual health check options associated with an . +/// +public class HealthCheckOptions +{ + + /// + /// Creates a new . + /// + /// An optional representing the initial delay applied after the application starts before executing the check. + /// An optional representing the individual period of the check. + public HealthCheckOptions(TimeSpan? delay = default, TimeSpan? period = default) + { + Delay = delay; + Period = period; + } + + /// + /// Gets the initial individual delay applied to the + /// individual HealthCheck after the application starts before executing. + /// The delay is applied once at startup, and does + /// not apply to subsequent iterations. + /// + public TimeSpan? Delay { get; } + + /// + /// Gets or sets the individual period used for the check. + /// + public TimeSpan? Period { get; } +} diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index 77ce51a7580d..d8e74d917d0f 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.Diagnostics.HealthChecks; @@ -25,8 +26,6 @@ public sealed class HealthCheckRegistration private Func _factory; private string _name; private TimeSpan _timeout; - private TimeSpan _delay; - private TimeSpan _period; /// /// Creates a new for an existing instance. @@ -39,7 +38,7 @@ public sealed class HealthCheckRegistration /// /// A list of tags that can be used for filtering health checks. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, instance, failureStatus, tags, default, default, default) + : this(name, instance, failureStatus, tags, default, default) { } @@ -55,7 +54,7 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, instance, failureStatus, tags, timeout, default, default) + : this(name, instance, failureStatus, tags, timeout, default) { } @@ -70,9 +69,8 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? delay, TimeSpan? period) + /// An optional representing the individual health check options. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckOptions? options) { if (name == null) { @@ -89,23 +87,12 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? throw new ArgumentOutOfRangeException(nameof(timeout)); } - if (delay < TimeSpan.Zero || delay == System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(delay)); - } - - if (period < TimeSpan.FromSeconds(1) && period != System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(period)); - } - _name = name; FailureStatus = failureStatus ?? HealthStatus.Unhealthy; Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = (_) => instance; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - _delay = delay ?? System.Threading.Timeout.InfiniteTimeSpan; // Use InfiniteTimeSpan as default delay for non-default HCs - _period = period ?? default; // Use TimeSpan.Zero as default period for non-default HCs + Options = options; } /// @@ -123,7 +110,7 @@ public HealthCheckRegistration( Func factory, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, factory, failureStatus, tags, default, default, default) + : this(name, factory, failureStatus, tags, default, default) { } @@ -144,7 +131,7 @@ public HealthCheckRegistration( HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, factory, failureStatus, tags, timeout, default, default) + : this(name, factory, failureStatus, tags, timeout, default) { } @@ -159,16 +146,14 @@ public HealthCheckRegistration( /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. + /// An optional representing the individual health check options. public HealthCheckRegistration( string name, Func factory, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, - TimeSpan? delay, - TimeSpan? period) + HealthCheckOptions? options) { if (name == null) { @@ -185,23 +170,12 @@ public HealthCheckRegistration( throw new ArgumentOutOfRangeException(nameof(timeout)); } - if (delay < TimeSpan.Zero || delay == System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(delay)); - } - - if (period <= TimeSpan.Zero && period != System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(period)); - } - _name = name; FailureStatus = failureStatus ?? HealthStatus.Unhealthy; Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = factory; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - _delay = delay ?? System.Threading.Timeout.InfiniteTimeSpan; // Use InfiniteTimeSpan as default delay for non-default HCs - _period = period ?? default; // Use TimeSpan.Zero as default period for non-default HCs + Options = options; } /// @@ -244,41 +218,9 @@ public TimeSpan Timeout } /// - /// Gets or sets the initial individual delay applied to the - /// individual HealthCheck after the application starts before executing. - /// The delay is applied once at startup, and does - /// not apply to subsequent iterations. The default value is 5 seconds. + /// Gets the individual options associated with a health check. /// - public TimeSpan Delay - { - get => _delay; - set - { - if (value < TimeSpan.Zero || value == System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentException($"The {nameof(Delay)} must not be infinite.", nameof(value)); - } - - _delay = value; - } - } - - /// - /// Gets or sets the individual period used for the check. - /// - public TimeSpan Period - { - get => _period; - set - { - if (value <= TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _period = value; - } - } + public HealthCheckOptions? Options { get; } /// /// Gets or sets the health check name. diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index be30a81b7a9b..4295a943edf9 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1,7 +1,8 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.set -> void +Microsoft.Extensions.DependencyInjection.HealthCheckOptions +Microsoft.Extensions.DependencyInjection.HealthCheckOptions.Delay.get -> System.TimeSpan? +Microsoft.Extensions.DependencyInjection.HealthCheckOptions.HealthCheckOptions(System.TimeSpan? delay = null, System.TimeSpan? period = null) -> void +Microsoft.Extensions.DependencyInjection.HealthCheckOptions.Period.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Options.get -> Microsoft.Extensions.DependencyInjection.HealthCheckOptions? diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 20836c61e4af..17cbd36628b4 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -46,13 +46,13 @@ public DefaultHealthCheckService( // Group healthcheck registrations by delay and period, to build a Dictionary<(TimeSpan, TimeSpan), List> _individualHealthChecksMap = (from r in _healthCheckServiceOptions.Value.Registrations where !IsDefaultHealthCheck(r) // Skip default HCs initiated with publisher - group r by (r.Delay, r.Period)).ToDictionary(g => g.Key, g => g.ToList()); + group r by (r.Options?.Delay ?? _healthCheckPublisherOptions.Value.Delay, r.Options?.Period ?? _healthCheckPublisherOptions.Value.Period)).ToDictionary(g => g.Key, g => g.ToList()); // Aggregate Timers for HealthCheckRegistration having the same delay and period _ = CreateTimers(_individualHealthChecksMap); } - private static bool IsDefaultHealthCheck(HealthCheckRegistration registration) => registration.Delay == Timeout.InfiniteTimeSpan && registration.Period == default; + private static bool IsDefaultHealthCheck(HealthCheckRegistration registration) => registration.Options == default; private Dictionary<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period), List> periodHealthChecksMap, CancellationToken cancellationToken = default) { @@ -74,8 +74,8 @@ public DefaultHealthCheckService( } }, null, - dueTime: delay == Timeout.InfiniteTimeSpan ? _healthCheckPublisherOptions.Value.Delay : delay, // Default to publisher Delay - period: period == default ? _healthCheckPublisherOptions.Value.Period : period) // Default to publisher Period + dueTime: delay, // Default to publisher Delay + period: period) // Default to publisher Period ); } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 3c50260d1c12..9831757f32bf 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -48,59 +48,16 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. + /// An optional representing the individual health check options. /// The . - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( - this IHealthChecksBuilder builder, - string name, - IHealthCheck instance, - HealthStatus? failureStatus = null, - IEnumerable? tags = null, - TimeSpan? timeout = null) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, default, default)); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// An instance. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used to filter health checks. - /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// The . - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddIndividualCheck( this IHealthChecksBuilder builder, string name, IHealthCheck instance, HealthStatus? failureStatus = null, IEnumerable? tags = null, TimeSpan? timeout = null, - TimeSpan? delay = null, - TimeSpan? period = null) + HealthCheckOptions? options = null) { if (builder == null) { @@ -117,7 +74,7 @@ public static IHealthChecksBuilder AddIndividualCheck( throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, delay, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, options)); } /// @@ -160,6 +117,7 @@ public static IHealthChecksBuilder AddIndividualCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -169,60 +127,12 @@ public static IHealthChecksBuilder AddIndividualCheck( /// [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( - this IHealthChecksBuilder builder, - string name, - HealthStatus? failureStatus = null, - IEnumerable? tags = null, - TimeSpan? timeout = null) where T : class, IHealthCheck - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout)); - - [UnconditionalSuppressMessage("Trimming", "IL2091", - Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] - static T GetServiceOrCreateInstance(IServiceProvider serviceProvider) => - ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The health check implementation type. - /// The . - /// The name of the health check. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used to filter health checks. - /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// The . - /// - /// This method will use to create the health check - /// instance when needed. If a service of type is registered in the dependency injection container - /// with any lifetime it will be used. Otherwise an instance of type will be constructed with - /// access to services from the dependency injection container. - /// - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddIndividualCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus = null, IEnumerable? tags = null, TimeSpan? timeout = null, - TimeSpan? delay = null, - TimeSpan? period = null) where T : class, IHealthCheck + HealthCheckOptions? options = null) where T : class, IHealthCheck { if (builder == null) { @@ -234,7 +144,7 @@ static T GetServiceOrCreateInstance(IServiceProvider serviceProvider) => throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, delay, period)); + return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, options)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] @@ -410,22 +320,20 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. /// A representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check /// instance when needed. Additional arguments can be provided to the constructor via . /// - public static IHealthChecksBuilder AddTypeActivatedIndividualCheck< + public static IHealthChecksBuilder AddTypeActivatedCheck< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, IEnumerable tags, TimeSpan timeout, - TimeSpan delay, - TimeSpan period, + HealthCheckOptions options, params object[] args) where T : class, IHealthCheck { if (builder == null) @@ -438,7 +346,7 @@ public static IHealthChecksBuilder AddTypeActivatedIndividualCheck< throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, delay, period)); + return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, options)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 71cf25196fde..f75c697bd455 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -41,54 +41,16 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( - this IHealthChecksBuilder builder, - string name, - Func check, - IEnumerable? tags = null, - TimeSpan? timeout = default) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (check == null) - { - throw new ArgumentNullException(nameof(check)); - } - - var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default, default)); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// The . - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddIndividualCheck( this IHealthChecksBuilder builder, string name, Func check, IEnumerable? tags = null, TimeSpan? timeout = default, - TimeSpan? delay = default, - TimeSpan? period = default) + HealthCheckOptions? options = default) { if (builder == null) { @@ -106,7 +68,7 @@ public static IHealthChecksBuilder AddIndividualCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); } /// @@ -135,54 +97,16 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( - this IHealthChecksBuilder builder, - string name, - Func check, - IEnumerable? tags = null, - TimeSpan? timeout = default) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (check == null) - { - throw new ArgumentNullException(nameof(check)); - } - - var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// The . - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddIndividualCheck( this IHealthChecksBuilder builder, string name, Func check, IEnumerable? tags = null, TimeSpan? timeout = default, - TimeSpan? delay = default, - TimeSpan? period = default) + HealthCheckOptions? options = default) { if (builder == null) { @@ -200,7 +124,7 @@ public static IHealthChecksBuilder AddIndividualCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); } /// @@ -229,54 +153,15 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. + /// An optional representing the individual health check options. /// The . - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( - this IHealthChecksBuilder builder, - string name, - Func> check, - IEnumerable? tags = null, - TimeSpan? timeout = default) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (check == null) - { - throw new ArgumentNullException(nameof(check)); - } - - var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, default, default)); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// The . - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddAsyncIndividualCheck( this IHealthChecksBuilder builder, string name, Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, - TimeSpan? delay = default, - TimeSpan? period = default) + HealthCheckOptions? options = default) { if (builder == null) { @@ -294,7 +179,7 @@ public static IHealthChecksBuilder AddAsyncIndividualCheck( } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); } /// @@ -323,56 +208,16 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( - this IHealthChecksBuilder builder, - string name, - Func> check, - IEnumerable? tags = null, - TimeSpan? timeout = default) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (check == null) - { - throw new ArgumentNullException(nameof(check)); - } - - var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - - /// The . - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddAsyncIndividualCheck( this IHealthChecksBuilder builder, string name, Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, - TimeSpan? delay = default, - TimeSpan? period = default) + HealthCheckOptions? options = default) { if (builder == null) { @@ -390,6 +235,6 @@ public static IHealthChecksBuilder AddAsyncIndividualCheck( } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, delay, period)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); } } diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index a6dae5afb4a7..1edb2d37ba61 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -22,20 +22,20 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.HealthCheckServ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.HealthCheckServiceOptions() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.Registrations.get -> System.Collections.Generic.ICollection! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 3a1600f2dfd9..1eba88866bad 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1,2 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, System.TimeSpan delay, System.TimeSpan period, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddIndividualCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, System.TimeSpan? delay = null, System.TimeSpan? period = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions! options, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index e36c184e925b..3fecb22219a5 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -140,7 +140,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes var service = CreateHealthChecksService(b => { - b.AddAsyncIndividualCheck("HealthyCheck", _ => + b.AddAsyncCheck("HealthyCheck", _ => { if (!healthyCheckInsideCheck.Task.IsCompleted) { @@ -150,9 +150,9 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }, healthyCheckTags, - period: TimeSpan.FromSeconds(2)); + options: new HealthCheckOptions(period: TimeSpan.FromSeconds(2))); - b.AddAsyncIndividualCheck("DegradedCheck", _ => + b.AddAsyncCheck("DegradedCheck", _ => { if (!degradedCheckInsideCheck.Task.IsCompleted) { @@ -162,9 +162,9 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes return Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)); }, degradedCheckTags, - period: TimeSpan.FromSeconds(3)); + options: new HealthCheckOptions(period: TimeSpan.FromSeconds(3))); - b.AddAsyncIndividualCheck("UnhealthyCheck", _ => + b.AddAsyncCheck("UnhealthyCheck", _ => { if (!unhealthyCheckInsideCheck.Task.IsCompleted) { @@ -174,7 +174,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes }, unhealthyCheckTags, - period: TimeSpan.FromSeconds(5)); + options: new HealthCheckOptions(period: TimeSpan.FromSeconds(5))); }); // Act @@ -259,7 +259,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); }); - b.AddAsyncIndividualCheck("Check3Period2", _ => + b.AddAsyncCheck("Check3Period2", _ => { if (!check3Period2InsideCheck.Task.IsCompleted) { @@ -267,9 +267,10 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA } return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); - }, period: TimeSpan.FromSeconds(2)); + }, + options: new HealthCheckOptions(period: TimeSpan.FromSeconds(2))); - b.AddAsyncIndividualCheck("Check4Period5", _ => + b.AddAsyncCheck("Check4Period5", _ => { if (!check4Period5InsideCheck.Task.IsCompleted) { @@ -277,9 +278,9 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA } return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); - }, period: TimeSpan.FromSeconds(5)); + }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(5))); - b.AddAsyncIndividualCheck("Check5Period7", _ => + b.AddAsyncCheck("Check5Period7", _ => { if (!check5Period7InsideCheck.Task.IsCompleted) { @@ -287,7 +288,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA } return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); - }, period: TimeSpan.FromSeconds(7)); + }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(7))); }, options => { @@ -848,11 +849,11 @@ public void CheckHealthAsync_IndividualPeriodicity_WorksInSingleThreadedSyncCont // Arrange var service = CreateHealthChecksService(b => { - b.AddAsyncIndividualCheck("test", async () => + b.AddAsyncCheck("test", async () => { await Task.Delay(10).ConfigureAwait(false); return HealthCheckResult.Healthy(); - }, period: TimeSpan.FromSeconds(4)); + }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(4))); }); var hangs = true; From 64dbf5b53ba46017950681d237a9b8d06e38aced Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 3 Aug 2022 19:03:01 +0100 Subject: [PATCH 50/91] Refactor API to solve binary breaking --- .../HealthChecksBuilderAddCheckExtensions.cs | 146 +++++++++--------- .../HealthChecksBuilderDelegateExtensions.cs | 105 +++++++++++-- .../HealthChecks/src/PublicAPI.Shipped.txt | 24 +-- .../HealthChecks/src/PublicAPI.Unshipped.txt | 8 +- 4 files changed, 185 insertions(+), 98 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 9831757f32bf..1052b7955782 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -31,9 +31,33 @@ public static IHealthChecksBuilder AddCheck( string name, IHealthCheck instance, HealthStatus? failureStatus, - IEnumerable tags) + IEnumerable? tags) { - return AddCheck(builder, name, instance, failureStatus, tags, default); + return AddCheck(builder, name, instance, failureStatus, tags, default, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// An instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + IHealthCheck instance, + HealthStatus? failureStatus, + IEnumerable? tags, + TimeSpan? timeout) + { + return AddCheck(builder, name, instance, failureStatus, tags, timeout, default); } /// @@ -50,14 +74,15 @@ public static IHealthChecksBuilder AddCheck( /// An optional representing the timeout of the check. /// An optional representing the individual health check options. /// The . + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, IHealthCheck instance, - HealthStatus? failureStatus = null, - IEnumerable? tags = null, - TimeSpan? timeout = null, - HealthCheckOptions? options = null) + HealthStatus? failureStatus = default, + IEnumerable? tags = default, + TimeSpan? timeout = default, + HealthCheckOptions? options = default) { if (builder == null) { @@ -100,9 +125,38 @@ public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, - IEnumerable tags) where T : class, IHealthCheck + IEnumerable? tags) where T : class, IHealthCheck + { + return AddCheck(builder, name, failureStatus, tags, default, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// An optional representing the timeout of the check. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. If a service of type is registered in the dependency injection container + /// with any lifetime it will be used. Otherwise an instance of type will be constructed with + /// access to services from the dependency injection container. + /// + public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + IEnumerable? tags, + TimeSpan? timeout) where T : class, IHealthCheck { - return AddCheck(builder, name, failureStatus, tags, default); + return AddCheck(builder, name, failureStatus, tags, timeout, default); } /// @@ -125,14 +179,13 @@ public static IHealthChecksBuilder AddCheck( /// with any lifetime it will be used. Otherwise an instance of type will be constructed with /// access to services from the dependency injection container. /// - [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, - HealthStatus? failureStatus = null, - IEnumerable? tags = null, - TimeSpan? timeout = null, - HealthCheckOptions? options = null) where T : class, IHealthCheck + HealthStatus? failureStatus = default, + IEnumerable? tags = default, + TimeSpan? timeout = default, + HealthCheckOptions? options = default) where T : class, IHealthCheck { if (builder == null) { @@ -171,17 +224,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, params object[] args) where T : class, IHealthCheck { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - return AddTypeActivatedCheck(builder, name, failureStatus: null, tags: null, args); + return AddTypeActivatedCheck(builder, name, failureStatus: default, tags: default, args); } /// @@ -207,17 +250,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< HealthStatus? failureStatus, params object[] args) where T : class, IHealthCheck { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - return AddTypeActivatedCheck(builder, name, failureStatus, tags: null, args); + return AddTypeActivatedCheck(builder, name, failureStatus, tags: default, timeout: default, options: default, args); } /// @@ -245,22 +278,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< IEnumerable? tags, params object[] args) where T : class, IHealthCheck { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags)); - - [UnconditionalSuppressMessage("Trimming", "IL2091", - Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] - T CreateInstance(IServiceProvider serviceProvider) => - ActivatorUtilities.CreateInstance(serviceProvider, args); + return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: default, options: default, args); } /// @@ -286,25 +304,11 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, - IEnumerable tags, - TimeSpan timeout, + IEnumerable? tags, + TimeSpan? timeout, params object[] args) where T : class, IHealthCheck { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout)); - - [UnconditionalSuppressMessage("Trimming", "IL2091", - Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] - T CreateInstance(IServiceProvider serviceProvider) => ActivatorUtilities.CreateInstance(serviceProvider, args); + return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: timeout, options: default, args); } /// @@ -331,9 +335,9 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, - IEnumerable tags, - TimeSpan timeout, - HealthCheckOptions options, + IEnumerable? tags, + TimeSpan? timeout, + HealthCheckOptions? options, params object[] args) where T : class, IHealthCheck { if (builder == null) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index f75c697bd455..4307d970e5e7 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -28,9 +28,28 @@ public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable tags) + IEnumerable? tags) + { + return AddCheck(builder, name, check, tags, default, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable? tags, + TimeSpan? timeout) { - return AddCheck(builder, name, check, tags, default); + return AddCheck(builder, name, check, tags, timeout, default); } /// @@ -48,7 +67,7 @@ public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable? tags = null, + IEnumerable? tags = default, TimeSpan? timeout = default, HealthCheckOptions? options = default) { @@ -68,7 +87,7 @@ public static IHealthChecksBuilder AddCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); } /// @@ -86,7 +105,26 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags) { - return AddCheck(builder, name, check, tags, default); + return AddCheck(builder, name, check, tags, default, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable? tags, + TimeSpan? timeout) + { + return AddCheck(builder, name, check, tags, timeout, default); } /// @@ -104,7 +142,7 @@ public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable? tags = null, + IEnumerable? tags = default, TimeSpan? timeout = default, HealthCheckOptions? options = default) { @@ -124,7 +162,7 @@ public static IHealthChecksBuilder AddCheck( } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); } /// @@ -140,9 +178,28 @@ public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, Func> check, - IEnumerable tags) + IEnumerable? tags) { - return AddAsyncCheck(builder, name, check, tags, default); + return AddAsyncCheck(builder, name, check, tags, default, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable? tags, + TimeSpan? timeout) + { + return AddAsyncCheck(builder, name, check, tags, timeout, default); } /// @@ -155,11 +212,12 @@ public static IHealthChecksBuilder AddAsyncCheck( /// An optional representing the timeout of the check. /// An optional representing the individual health check options. /// The . + [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, Func> check, - IEnumerable? tags = null, + IEnumerable? tags = default, TimeSpan? timeout = default, HealthCheckOptions? options = default) { @@ -179,7 +237,7 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); } /// @@ -195,9 +253,28 @@ public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, Func> check, - IEnumerable tags) + IEnumerable? tags) + { + return AddAsyncCheck(builder, name, check, tags, default, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable? tags, + TimeSpan? timeout) { - return AddAsyncCheck(builder, name, check, tags, default); + return AddAsyncCheck(builder, name, check, tags, timeout, default); } /// @@ -235,6 +312,6 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); } } diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index 1edb2d37ba61..7f859a51be86 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -22,20 +22,20 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.HealthCheckServ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.HealthCheckServiceOptions() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.Registrations.get -> System.Collections.Generic.ICollection! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 1eba88866bad..fe05273fa204 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,2 +1,8 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions! options, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From 23824b947598428893dc85e84685be81b6cc8ee8 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 4 Aug 2022 00:21:46 +0100 Subject: [PATCH 51/91] Suppress RS0027 --- .../DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 1052b7955782..99df1e8044f9 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -179,6 +179,7 @@ public static IHealthChecksBuilder AddCheck( /// with any lifetime it will be used. Otherwise an instance of type will be constructed with /// access to services from the dependency injection container. /// + [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, From 0c464ca900a4e87e96dda112e912fd04c78436a8 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 4 Aug 2022 15:20:50 +0100 Subject: [PATCH 52/91] Migrate Public APIs to ValueTask --- .../HealthChecks/src/DelegateHealthCheck.cs | 6 +-- .../HealthChecksBuilderDelegateExtensions.cs | 16 +++---- .../HealthChecks/src/PublicAPI.Shipped.txt | 8 ++-- .../HealthChecks/src/PublicAPI.Unshipped.txt | 4 +- .../test/DefaultHealthCheckServiceTest.cs | 44 +++++++++---------- .../HealthChecksBuilderTest.cs | 10 ++--- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs index ac7b82a0f80d..ce28407dad08 100644 --- a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs +++ b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs @@ -13,13 +13,13 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// internal sealed class DelegateHealthCheck : IHealthCheck { - private readonly Func> _check; + private readonly Func> _check; /// /// Create an instance of from the specified delegate. /// /// A delegate which provides the code to execute when the health check is run. - public DelegateHealthCheck(Func> check) + public DelegateHealthCheck(Func> check) { _check = check ?? throw new ArgumentNullException(nameof(check)); } @@ -30,5 +30,5 @@ public DelegateHealthCheck(Func> chec /// A context object associated with the current execution. /// A that can be used to cancel the health check. /// A that completes when the health check has finished, yielding the status of the component being checked. - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken); + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken).AsTask(); } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 4307d970e5e7..d14485e182af 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -86,7 +86,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); + var instance = new DelegateHealthCheck((ct) => new ValueTask(check())); return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); } @@ -161,7 +161,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); + var instance = new DelegateHealthCheck((ct) => new ValueTask(check(ct))); return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); } @@ -177,7 +177,7 @@ public static IHealthChecksBuilder AddCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags) { return AddAsyncCheck(builder, name, check, tags, default, default); @@ -195,7 +195,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags, TimeSpan? timeout) { @@ -216,7 +216,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags = default, TimeSpan? timeout = default, HealthCheckOptions? options = default) @@ -252,7 +252,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags) { return AddAsyncCheck(builder, name, check, tags, default, default); @@ -270,7 +270,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags, TimeSpan? timeout) { @@ -291,7 +291,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, HealthCheckOptions? options = default) diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index 7f859a51be86..2d8da1fe23c5 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -30,10 +30,10 @@ static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExten static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index fe05273fa204..01def08ac1d6 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -2,7 +2,7 @@ static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 3fecb22219a5..7e4299186ee2 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -31,11 +31,11 @@ public void Constructor_ThrowsUsefulExceptionForDuplicateNames() serviceCollection.AddLogging(); serviceCollection.AddOptions(); serviceCollection.AddHealthChecks() - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); + .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))); var services = serviceCollection.BuildServiceProvider(); @@ -72,9 +72,9 @@ public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); - b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); + b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); + b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); }); // Act @@ -147,7 +147,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes healthyCheckInsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); }, healthyCheckTags, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(2))); @@ -159,7 +159,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes degradedCheckInsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)); + return new ValueTask(HealthCheckResult.Degraded(DegradedMessage)); }, degradedCheckTags, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(3))); @@ -170,7 +170,7 @@ public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesRes { unhealthyCheckInsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)); + return new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)); }, unhealthyCheckTags, @@ -246,7 +246,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA check1PeriodDefaultInsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); }); b.AddAsyncCheck("Check2PeriodDefault", _ => @@ -256,7 +256,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA check2PeriodDefaultInsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); }); b.AddAsyncCheck("Check3Period2", _ => @@ -266,7 +266,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA check3Period2InsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(2))); @@ -277,7 +277,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA check4Period5InsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(5))); b.AddAsyncCheck("Check5Period7", _ => @@ -287,7 +287,7 @@ public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsA check5Period7InsideCheck.SetResult(null); } - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(7))); }, options => @@ -424,9 +424,9 @@ public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); - b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); - b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); + b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); }); // Act @@ -530,8 +530,8 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu var service = CreateHealthChecksService(b => { b.AddAsyncCheck("Throws", ct => throw thrownException); - b.AddAsyncCheck("Faults", ct => Task.FromException(faultedException)); - b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy())); + b.AddAsyncCheck("Faults", ct => new ValueTask(Task.FromException(faultedException))); + b.AddAsyncCheck("Succeeds", ct => new ValueTask(HealthCheckResult.Healthy())); }); // Act @@ -581,7 +581,7 @@ public async Task CheckHealthAsync_SetsUpALoggerScopeForEachCheck() Assert.Equal("TestScope", item.Value); }); }); - return Task.FromResult(HealthCheckResult.Healthy()); + return new ValueTask(HealthCheckResult.Healthy()); }); var loggerFactory = new TestLoggerFactory(sink, enabled: true); diff --git a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs index 048ca2547dfd..b76c169bae3b 100644 --- a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs +++ b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs @@ -20,7 +20,7 @@ public void AddCheck_Instance() // Arrange var instance = new DelegateHealthCheck((_) => { - return Task.FromResult(HealthCheckResult.Healthy()); + return new ValueTask(HealthCheckResult.Healthy()); }); var services = CreateServices(); @@ -160,7 +160,7 @@ public void AddAsyncDelegateCheck_NoArg() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", () => { - return Task.FromResult(HealthCheckResult.Healthy()); + return new ValueTask(HealthCheckResult.Healthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -183,7 +183,7 @@ public void AddAsyncDelegateCheck_CancellationToken() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", (_) => { - return Task.FromResult(HealthCheckResult.Unhealthy()); + return new ValueTask(HealthCheckResult.Unhealthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -205,10 +205,10 @@ public void ChecksCanBeRegisteredInMultipleCallsToAddHealthChecks() var services = new ServiceCollection(); services .AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy())); + .AddAsyncCheck("Foo", () => new ValueTask(HealthCheckResult.Healthy())); services .AddHealthChecks() - .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy())); + .AddAsyncCheck("Bar", () => new ValueTask(HealthCheckResult.Healthy())); // Act var options = services.BuildServiceProvider().GetRequiredService>(); From 9a076c66139271b05453d824f60cc2c51d00e6d8 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 4 Aug 2022 20:03:39 +0100 Subject: [PATCH 53/91] Revert DefaultHealthCheckService impl --- .../src/DefaultHealthCheckService.cs | 138 +------ .../test/DefaultHealthCheckServiceTest.cs | 352 +----------------- 2 files changed, 33 insertions(+), 457 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 17cbd36628b4..916ef2f4749d 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -3,7 +3,6 @@ using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -19,95 +18,32 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; internal sealed partial class DefaultHealthCheckService : HealthCheckService { private readonly IServiceScopeFactory _scopeFactory; - private readonly IOptions _healthCheckServiceOptions; - private readonly IOptions _healthCheckPublisherOptions; + private readonly IOptions _options; private readonly ILogger _logger; - private readonly ConcurrentQueue> _healthReportEntriesQueue; - private readonly Dictionary<(TimeSpan Delay, TimeSpan Period), List> _individualHealthChecksMap; public DefaultHealthCheckService( IServiceScopeFactory scopeFactory, - IOptions healthCheckServiceOptions, - IOptions healthCheckPublisherOptions, + IOptions options, ILogger logger) { _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); - _healthCheckServiceOptions = healthCheckServiceOptions ?? throw new ArgumentNullException(nameof(healthCheckServiceOptions)); - _healthCheckPublisherOptions = healthCheckPublisherOptions ?? throw new ArgumentNullException(nameof(healthCheckPublisherOptions)); + _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - // We're specifically going out of our way to do this at startup time. We want to make sure you - // get any kind of health-check related error as early as possible. Waiting until someone - // actually tries to **run** health checks would be real baaaaad. - ValidateRegistrations(_healthCheckServiceOptions.Value.Registrations); - - _healthReportEntriesQueue = new ConcurrentQueue>(); - - // Group healthcheck registrations by delay and period, to build a Dictionary<(TimeSpan, TimeSpan), List> - _individualHealthChecksMap = (from r in _healthCheckServiceOptions.Value.Registrations - where !IsDefaultHealthCheck(r) // Skip default HCs initiated with publisher - group r by (r.Options?.Delay ?? _healthCheckPublisherOptions.Value.Delay, r.Options?.Period ?? _healthCheckPublisherOptions.Value.Period)).ToDictionary(g => g.Key, g => g.ToList()); - - // Aggregate Timers for HealthCheckRegistration having the same delay and period - _ = CreateTimers(_individualHealthChecksMap); - } - - private static bool IsDefaultHealthCheck(HealthCheckRegistration registration) => registration.Options == default; - - private Dictionary<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period), List> periodHealthChecksMap, CancellationToken cancellationToken = default) - { - return periodHealthChecksMap.Select(m => CreateTimer(m.Key.Delay, m.Key.Period, m.Value, cancellationToken)).ToDictionary(kv => kv.Key, kv => kv.Value); - } - - private KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimer(TimeSpan delay, TimeSpan period, List registrations, CancellationToken cancellationToken = default) - { - return new KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer>( - (delay, period), - NonCapturingTimer.Create( - async (state) => - { - var checksToRun = registrations.Where(r => ByPredicate(r)).ToList(); // Apply publisher predicate - var entries = await RunChecksAsync(checksToRun, cancellationToken).ConfigureAwait(false); - foreach (var entry in entries) - { - _healthReportEntriesQueue.Enqueue(entry); - } - }, - null, - dueTime: delay, // Default to publisher Delay - period: period) // Default to publisher Period - ); } - public override async Task CheckHealthAsync(Func? predicate, CancellationToken cancellationToken = default) + public override async Task CheckHealthAsync( + Func? predicate, + CancellationToken cancellationToken = default) { - // In the current design, where HCs with default periodicity are initiated during CheckHealthAsync, - // individual HCs cannot use the predicate passed in this method (those are initiated in the cctor of DefaultHealthCheckService instead) - - var totalTime = ValueStopwatch.StartNew(); - Log.HealthCheckProcessingBegin(_logger); - - // Filter registrations by default HC and applied predicate - var registrationsDefaultPeriod = _healthCheckServiceOptions.Value.Registrations.Where(r => IsDefaultHealthCheck(r) && ByPredicate(r, predicate)).ToList(); - - // Run healthchecks with default same delay and period as publisher - var healthReportEntriesValues = await RunChecksAsync(registrationsDefaultPeriod, cancellationToken).ConfigureAwait(false); - - // Collect health report entries from previously run periodic healthchecks within the default period - while (_healthReportEntriesQueue.TryDequeue(out var entry)) + var registrations = _options.Value.Registrations; + if (predicate != null) { - healthReportEntriesValues.Add(entry); + registrations = registrations.Where(predicate).ToArray(); } - var totalElapsedTime = totalTime.GetElapsedTime(); - var report = new HealthReport(new HealthReportEntryDictionary(healthReportEntriesValues, StringComparer.OrdinalIgnoreCase), totalElapsedTime); - Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); - - return report; - } + var totalTime = ValueStopwatch.StartNew(); + Log.HealthCheckProcessingBegin(_logger); - private async Task>> RunChecksAsync(ICollection registrations, CancellationToken cancellationToken = default) - { var tasks = new Task[registrations.Count]; var index = 0; @@ -118,19 +54,20 @@ private async Task>> RunChecksAsync await Task.WhenAll(tasks).ConfigureAwait(false); - var entries = new List>(); - index = 0; + var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var registration in registrations) { - var healthReportEntry = tasks[index++].Result; - entries.Add(new KeyValuePair(registration.Name, healthReportEntry)); + entries[registration.Name] = tasks[index++].Result; } - return entries; + var totalElapsedTime = totalTime.GetElapsedTime(); + var report = new HealthReport(entries, totalElapsedTime); + Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); + return report; } - private async Task RunCheckAsync(HealthCheckRegistration registration, CancellationToken cancellationToken = default) + private async Task RunCheckAsync(HealthCheckRegistration registration, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -216,45 +153,6 @@ private async Task RunCheckAsync(HealthCheckRegistration regi } } - private static void ValidateRegistrations(IEnumerable registrations) - { - // Scan the list for duplicate names to provide a better error if there are duplicates. - - StringBuilder? builder = null; - var distinctRegistrations = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var registration in registrations) - { - if (!distinctRegistrations.Add(registration.Name)) - { - builder ??= new StringBuilder("Duplicate health checks were registered with the name(s): "); - - builder.Append(registration.Name).Append(", "); - } - } - - if (builder is not null) - { - throw new ArgumentException(builder.ToString(0, builder.Length - 2), nameof(registrations)); - } - } - - private bool ByPredicate(HealthCheckRegistration registration, Func? predicate = default) - { - // Default to the HealthCheckPublisher predicate - if (predicate == default) - { - predicate = _healthCheckPublisherOptions.Value.Predicate; - } - - if (predicate == default) - { - return true; - } - - return predicate(registration); - } - internal static class EventIds { public const int HealthCheckProcessingBeginId = 100; diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 7e4299186ee2..7693aa6f404f 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -20,37 +20,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; public class DefaultHealthCheckServiceTest { - [Fact] - public void Constructor_ThrowsUsefulExceptionForDuplicateNames() - { - // Arrange - // - // Doing this the old fashioned way so we can verify that the exception comes - // from the constructor. - var serviceCollection = new ServiceCollection(); - serviceCollection.AddLogging(); - serviceCollection.AddOptions(); - serviceCollection.AddHealthChecks() - .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Bar", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))); - - var services = serviceCollection.BuildServiceProvider(); - - var scopeFactory = services.GetRequiredService(); - var healthCheckServiceOptionsOptions = services.GetRequiredService>(); - var healthCheckPublisherOptions = services.GetRequiredService>(); - var logger = services.GetRequiredService>(); - - // Act - var exception = Assert.Throws(() => new DefaultHealthCheckService(scopeFactory, healthCheckServiceOptionsOptions, healthCheckPublisherOptions, logger)); - - // Assert - Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message); - } - [Fact] public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() { @@ -116,255 +85,6 @@ public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() }); } - [Fact] - public async Task CheckAsync_IndividualPeriodicity_RunsAllChecksAndAggregatesResultsAsync() - { - const string DataKey = "Foo"; - const string DataValue = "Bar"; - const string DegradedMessage = "I'm not feeling so good"; - const string UnhealthyMessage = "Halp!"; - const string HealthyMessage = "Everything is A-OK"; - var exception = new Exception("Things are pretty bad!"); - var healthyCheckTags = new List { "healthy-check-tag" }; - var degradedCheckTags = new List { "degraded-check-tag" }; - var unhealthyCheckTags = new List { "unhealthy-check-tag" }; - - // Arrange - var data = new Dictionary() - { - { DataKey, DataValue } - }; - var healthyCheckInsideCheck = new TaskCompletionSource(); - var degradedCheckInsideCheck = new TaskCompletionSource(); - var unhealthyCheckInsideCheck = new TaskCompletionSource(); - - var service = CreateHealthChecksService(b => - { - b.AddAsyncCheck("HealthyCheck", _ => - { - if (!healthyCheckInsideCheck.Task.IsCompleted) - { - healthyCheckInsideCheck.SetResult(null); - } - - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); - }, - healthyCheckTags, - options: new HealthCheckOptions(period: TimeSpan.FromSeconds(2))); - - b.AddAsyncCheck("DegradedCheck", _ => - { - if (!degradedCheckInsideCheck.Task.IsCompleted) - { - degradedCheckInsideCheck.SetResult(null); - } - - return new ValueTask(HealthCheckResult.Degraded(DegradedMessage)); - }, - degradedCheckTags, - options: new HealthCheckOptions(period: TimeSpan.FromSeconds(3))); - - b.AddAsyncCheck("UnhealthyCheck", _ => - { - if (!unhealthyCheckInsideCheck.Task.IsCompleted) - { - unhealthyCheckInsideCheck.SetResult(null); - } - return new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)); - - }, - unhealthyCheckTags, - options: new HealthCheckOptions(period: TimeSpan.FromSeconds(5))); - }); - - // Act - await Task.WhenAll(healthyCheckInsideCheck.Task, degradedCheckInsideCheck.Task, unhealthyCheckInsideCheck.Task, Task.Delay(15000)); - var results = await service.CheckHealthAsync(); - - // Assert - // All individual checks to be triggered at least once - Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key).GroupBy(kvp => kvp.Key).Select(l => l.First()), - actual => - { - Assert.Equal("DegradedCheck", actual.Key); - Assert.Equal(DegradedMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Degraded, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Empty(actual.Value.Data); - Assert.Equal(actual.Value.Tags, degradedCheckTags); - }, - actual => - { - Assert.Equal("HealthyCheck", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - Assert.Equal(actual.Value.Tags, healthyCheckTags); - }, - actual => - { - Assert.Equal("UnhealthyCheck", actual.Key); - Assert.Equal(UnhealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); - Assert.Same(exception, actual.Value.Exception); - Assert.Empty(actual.Value.Data); - Assert.Equal(actual.Value.Tags, unhealthyCheckTags); - }); - } - - [Fact] - public async Task CheckAsync_MixedPeriodicity_RunsAllChecksAndAggregatesResultsAsync() - { - const string DataKey = "Foo"; - const string DataValue = "Bar"; - const string HealthyMessage = "Everything is A-OK"; - - var check1PeriodDefaultInsideCheck = new TaskCompletionSource(); - var check2PeriodDefaultInsideCheck = new TaskCompletionSource(); - var check3Period2InsideCheck = new TaskCompletionSource(); - var check4Period5InsideCheck = new TaskCompletionSource(); - var check5Period7InsideCheck = new TaskCompletionSource(); - - // Arrange - var data = new Dictionary() - { - { DataKey, DataValue } - }; - - var service = CreateHealthChecksService(b => - { - b.AddAsyncCheck("Check1PeriodDefault", _ => - { - if (!check1PeriodDefaultInsideCheck.Task.IsCompleted) - { - check1PeriodDefaultInsideCheck.SetResult(null); - } - - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); - }); - - b.AddAsyncCheck("Check2PeriodDefault", _ => - { - if (!check2PeriodDefaultInsideCheck.Task.IsCompleted) - { - check2PeriodDefaultInsideCheck.SetResult(null); - } - - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); - }); - - b.AddAsyncCheck("Check3Period2", _ => - { - if (!check3Period2InsideCheck.Task.IsCompleted) - { - check3Period2InsideCheck.SetResult(null); - } - - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); - }, - options: new HealthCheckOptions(period: TimeSpan.FromSeconds(2))); - - b.AddAsyncCheck("Check4Period5", _ => - { - if (!check4Period5InsideCheck.Task.IsCompleted) - { - check4Period5InsideCheck.SetResult(null); - } - - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); - }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(5))); - - b.AddAsyncCheck("Check5Period7", _ => - { - if (!check5Period7InsideCheck.Task.IsCompleted) - { - check5Period7InsideCheck.SetResult(null); - } - - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)); - }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(7))); - }, - options => - { - options.Period = TimeSpan.FromSeconds(2); - }); - - // Act - await Task.WhenAll(check3Period2InsideCheck.Task, check4Period5InsideCheck.Task, check5Period7InsideCheck.Task, Task.Delay(15000)); - var results = await service.CheckHealthAsync(); - - // Assert - // All individual checks to be triggered at least once - Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key).GroupBy(kvp => kvp.Key).Select(l => l.First()), - actual => - { - Assert.Equal("Check1PeriodDefault", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - }, - actual => - { - Assert.Equal("Check2PeriodDefault", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - }, - actual => - { - Assert.Equal("Check3Period2", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - }, - actual => - { - Assert.Equal("Check4Period5", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - }, - actual => - { - Assert.Equal("Check5Period7", actual.Key); - Assert.Equal(HealthyMessage, actual.Value.Description); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Null(actual.Value.Exception); - Assert.Collection(actual.Value.Data, item => - { - Assert.Equal(DataKey, item.Key); - Assert.Equal(DataValue, item.Value); - }); - }); - } - [Fact] public async Task CheckAsync_TagsArePresentInHealthReportEntryIfExceptionOccurs() { @@ -467,7 +187,7 @@ public async Task CheckHealthAsync_SetsRegistrationForEachCheck() // Assert Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key), + results.Entries, actual => { Assert.Equal("A", actual.Key); @@ -539,7 +259,14 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu // Assert Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key), + results.Entries, + actual => + { + Assert.Equal("Throws", actual.Key); + Assert.Equal(thrownException.Message, actual.Value.Description); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + Assert.Same(thrownException, actual.Value.Exception); + }, actual => { Assert.Equal("Faults", actual.Key); @@ -553,13 +280,6 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu Assert.Null(actual.Value.Description); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); Assert.Null(actual.Value.Exception); - }, - actual => - { - Assert.Equal("Throws", actual.Key); - Assert.Equal(thrownException.Message, actual.Value.Description); - Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); - Assert.Same(thrownException, actual.Value.Exception); }); } @@ -597,9 +317,7 @@ public async Task CheckHealthAsync_SetsUpALoggerScopeForEachCheck() var results = await service.CheckHealthAsync(); // Assert - Assert.Collection( - results.Entries, - actual => + Assert.Collection(results.Entries, actual => { Assert.Equal("TestScope", actual.Key); Assert.Equal(HealthStatus.Healthy, actual.Value.Status); @@ -672,7 +390,7 @@ public async Task CheckHealthAsync_CheckCanDependOnScopedService_per_check() // Assert Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key), + results.Entries, actual => { Assert.Equal("Test", actual.Key); @@ -702,8 +420,7 @@ public async Task CheckHealthAsync_CheckCanHaveScopedDisposableDependencies() var results = await service.CheckHealthAsync(); // Assert - var healthCheck = (DisposableDependeciesCheck)results.Entries - .Single().Value.Data.Single().Value; + var healthCheck = (DisposableDependeciesCheck)results.Entries.Single().Value.Data.Single().Value; Assert.True(healthCheck.SynchronousDisposable.IsDisposed); Assert.True(healthCheck.AsyncOnlyDisposable.IsAsyncDisposed); @@ -770,8 +487,7 @@ public async Task CheckHealthAsync_ChecksAreRunInParallel() await checkHealthTask; // Assert - Assert.Collection( - checkHealthTask.Result.Entries.OrderBy(kvp => kvp.Key), + Assert.Collection(checkHealthTask.Result.Entries, entry => { Assert.Equal("test1", entry.Key); @@ -843,39 +559,6 @@ public void CheckHealthAsync_WorksInSingleThreadedSyncContext() Assert.False(hangs); } - [Fact] - public void CheckHealthAsync_IndividualPeriodicity_WorksInSingleThreadedSyncContext() - { - // Arrange - var service = CreateHealthChecksService(b => - { - b.AddAsyncCheck("test", async () => - { - await Task.Delay(10).ConfigureAwait(false); - return HealthCheckResult.Healthy(); - }, options: new HealthCheckOptions(period: TimeSpan.FromSeconds(4))); - }); - - var hangs = true; - - // Act - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) - { - var token = cts.Token; - token.Register(() => throw new OperationCanceledException(token)); - - SingleThreadedSynchronizationContext.Run(() => - { - // Act - service.CheckHealthAsync(token).GetAwaiter().GetResult(); - hangs = false; - }); - } - - // Assert - Assert.False(hangs); - } - [Fact] public async Task CheckHealthAsync_WithFailureStatus() { @@ -892,7 +575,7 @@ public async Task CheckHealthAsync_WithFailureStatus() // Assert Assert.Collection( - results.Entries.OrderBy(kvp => kvp.Key), + results.Entries, actual => { Assert.Equal("degraded", actual.Key); @@ -910,7 +593,7 @@ public async Task CheckHealthAsync_WithFailureStatus() }); } - private static DefaultHealthCheckService CreateHealthChecksService(Action configure, Action? healthCheckPublisherOptions = default) + private static DefaultHealthCheckService CreateHealthChecksService(Action configure) { var services = new ServiceCollection(); services.AddLogging(); @@ -922,11 +605,6 @@ private static DefaultHealthCheckService CreateHealthChecksService(Action(healthCheckPublisherOptions); - } - return (DefaultHealthCheckService)services.BuildServiceProvider(validateScopes: true).GetRequiredService(); } From 578398cda623a7649d00ee4a16c264042775041c Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 4 Aug 2022 20:04:57 +0100 Subject: [PATCH 54/91] Update HealthCheckPublisherHostedService to run individual HCs with cancellation --- .../Abstractions/src/HealthCheckOptions.cs | 28 +++- .../src/HealthCheckRegistration.cs | 1 - .../Abstractions/src/PublicAPI.Unshipped.txt | 17 ++- .../src/HealthCheckPublisherHostedService.cs | 121 +++++++++++++++--- .../src/HealthCheckServiceOptions.cs | 2 +- .../HealthChecks/src/PublicAPI.Unshipped.txt | 14 +- .../HealthCheckPublisherHostedServiceTest.cs | 51 ++++++++ 7 files changed, 198 insertions(+), 36 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs b/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs index e25f6255b283..99678ecc4747 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs @@ -2,13 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; -namespace Microsoft.Extensions.DependencyInjection; +namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// /// Represent the individual health check options associated with an . /// -public class HealthCheckOptions +public class HealthCheckOptions : IEquatable { /// @@ -34,4 +35,27 @@ public HealthCheckOptions(TimeSpan? delay = default, TimeSpan? period = default) /// Gets or sets the individual period used for the check. /// public TimeSpan? Period { get; } + + /// + public override bool Equals(object? obj) + { + return Equals(obj as HealthCheckOptions); + } + + /// + public bool Equals(HealthCheckOptions? other) + { + return other is not null && + EqualityComparer.Default.Equals(Delay, other.Delay) && + EqualityComparer.Default.Equals(Period, other.Period); + } + + /// + public override int GetHashCode() + { + var hashCode = -1368980644; + hashCode = hashCode * -1521134295 + Delay.GetHashCode(); + hashCode = hashCode * -1521134295 + Period.GetHashCode(); + return hashCode; + } } diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index d8e74d917d0f..0a00d4e1f712 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.Diagnostics.HealthChecks; diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 4295a943edf9..485ccdf0994e 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1,8 +1,11 @@ #nullable enable -Microsoft.Extensions.DependencyInjection.HealthCheckOptions -Microsoft.Extensions.DependencyInjection.HealthCheckOptions.Delay.get -> System.TimeSpan? -Microsoft.Extensions.DependencyInjection.HealthCheckOptions.HealthCheckOptions(System.TimeSpan? delay = null, System.TimeSpan? period = null) -> void -Microsoft.Extensions.DependencyInjection.HealthCheckOptions.Period.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Options.get -> Microsoft.Extensions.DependencyInjection.HealthCheckOptions? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Delay.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Equals(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? other) -> bool +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.HealthCheckOptions(System.TimeSpan? delay = null, System.TimeSpan? period = null) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Period.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Options.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? +override Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Equals(object? obj) -> bool +override Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.GetHashCode() -> int diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index f96b2dbf324c..51cef14671ee 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; @@ -16,17 +18,21 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; internal sealed partial class HealthCheckPublisherHostedService : IHostedService { private readonly HealthCheckService _healthCheckService; - private readonly IOptions _options; + private readonly IOptions _healthCheckServiceOptions; + private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; + private readonly HealthCheckOptions _defaultHealthCheckOptions; + private readonly Dictionary> _healthChecksByOptions; + private Dictionary? _timersByOptions; private readonly CancellationTokenSource _stopping; - private Timer? _timer; private CancellationTokenSource? _runTokenSource; public HealthCheckPublisherHostedService( HealthCheckService healthCheckService, - IOptions options, + IOptions healthCheckServiceOptions, + IOptions healthCheckPublisherOptions, ILogger logger, IEnumerable publishers) { @@ -35,9 +41,14 @@ public HealthCheckPublisherHostedService( throw new ArgumentNullException(nameof(healthCheckService)); } - if (options == null) + if (healthCheckServiceOptions == null) { - throw new ArgumentNullException(nameof(options)); + throw new ArgumentNullException(nameof(healthCheckServiceOptions)); + } + + if (healthCheckPublisherOptions == null) + { + throw new ArgumentNullException(nameof(healthCheckPublisherOptions)); } if (logger == null) @@ -51,16 +62,28 @@ public HealthCheckPublisherHostedService( } _healthCheckService = healthCheckService; - _options = options; + _healthCheckServiceOptions = healthCheckServiceOptions; + _healthCheckPublisherOptions = healthCheckPublisherOptions; _logger = logger; _publishers = publishers.ToArray(); _stopping = new CancellationTokenSource(); + + // We're specifically going out of our way to do this at startup time. We want to make sure you + // get any kind of health-check related error as early as possible. Waiting until someone + // actually tries to **run** health checks would be real baaaaad. + ValidateRegistrations(_healthCheckServiceOptions.Value.Registrations); + + _defaultHealthCheckOptions = new HealthCheckOptions(_healthCheckPublisherOptions.Value.Delay, _healthCheckPublisherOptions.Value.Period); + // Group healthcheck registrations by delay and period, to build a Dictionary<(TimeSpan, TimeSpan), List> + // For HCs with no Delay or Period, we default to the publisher values + _healthChecksByOptions = (from r in _healthCheckServiceOptions.Value.Registrations + group r by r.Options ?? _defaultHealthCheckOptions).ToDictionary(g => g.Key, g => g.ToList()); } internal bool IsStopping => _stopping.IsCancellationRequested; - internal bool IsTimerRunning => _timer != null; + internal bool IsTimerRunning => _timersByOptions != null; public Task StartAsync(CancellationToken cancellationToken = default) { @@ -71,7 +94,7 @@ public Task StartAsync(CancellationToken cancellationToken = default) // IMPORTANT - make sure this is the last thing that happens in this method. The timer can // fire before other code runs. - _timer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _options.Value.Delay, period: _options.Value.Period); + _timersByOptions = CreateTimers(_healthChecksByOptions); return Task.CompletedTask; } @@ -92,16 +115,37 @@ public Task StopAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - _timer?.Dispose(); - _timer = null; + if (_timersByOptions != null) + { + foreach (var timer in _timersByOptions.Values) + { + timer.Dispose(); + } + + _timersByOptions = null; + } return Task.CompletedTask; } - // Yes, async void. We need to be async. We need to be void. We handle the exceptions in RunAsync - private async void Timer_Tick(object? state) + private Dictionary CreateTimers(IReadOnlyDictionary> periodHealthChecksMap) + { + return periodHealthChecksMap.Select(m => CreateTimer(m.Key)).ToDictionary(kv => kv.Key, kv => kv.Value); + } + + private KeyValuePair CreateTimer(HealthCheckOptions healthCheckOptions) { - await RunAsync().ConfigureAwait(false); + return new KeyValuePair( + healthCheckOptions, + NonCapturingTimer.Create( + async (state) => + { + await RunAsync(healthCheckOptions: healthCheckOptions).ConfigureAwait(false); + }, + null, + dueTime: healthCheckOptions.Delay ?? _healthCheckPublisherOptions.Value.Delay, // Default to publisher Delay + period: healthCheckOptions.Period ?? _healthCheckPublisherOptions.Value.Period) // Default to publisher Period + ); } // Internal for testing @@ -111,21 +155,26 @@ internal void CancelToken() } // Internal for testing - internal async Task RunAsync() + internal async Task RunAsync(HealthCheckOptions? healthCheckOptions = default) { + if (healthCheckOptions == default) + { + healthCheckOptions = _defaultHealthCheckOptions; + } + var duration = ValueStopwatch.StartNew(); Logger.HealthCheckPublisherProcessingBegin(_logger); CancellationTokenSource? cancellation = null; try { - var timeout = _options.Value.Timeout; + var timeout = _healthCheckPublisherOptions.Value.Timeout; cancellation = CancellationTokenSource.CreateLinkedTokenSource(_stopping.Token); _runTokenSource = cancellation; cancellation.CancelAfter(timeout); - await RunAsyncCore(cancellation.Token).ConfigureAwait(false); + await RunAsyncCore(healthCheckOptions, cancellation.Token).ConfigureAwait(false); Logger.HealthCheckPublisherProcessingEnd(_logger, duration.GetElapsedTime()); } @@ -145,13 +194,26 @@ internal async Task RunAsync() } } - private async Task RunAsyncCore(CancellationToken cancellationToken) + private async Task RunAsyncCore(HealthCheckOptions healthCheckOptions, CancellationToken cancellationToken) { // Forcibly yield - we want to unblock the timer thread. await Task.Yield(); + // Concatenate predicates - we only run HCs with the set delay and period + var withOptionsPredicate = (HealthCheckRegistration r) => + { + var hasOptions = (r.Options == default && healthCheckOptions == _defaultHealthCheckOptions) || + r.Options == healthCheckOptions; + if (_healthCheckPublisherOptions?.Value.Predicate == null) + { + return hasOptions; + } + + return hasOptions && _healthCheckPublisherOptions.Value.Predicate(r); + }; + // The health checks service does it's own logging, and doesn't throw exceptions. - var report = await _healthCheckService.CheckHealthAsync(_options.Value.Predicate, cancellationToken).ConfigureAwait(false); + var report = await _healthCheckService.CheckHealthAsync(withOptionsPredicate, cancellationToken).ConfigureAwait(false); var publishers = _publishers; var tasks = new Task[publishers.Length]; @@ -191,6 +253,29 @@ private async Task RunPublisherAsync(IHealthCheckPublisher publisher, HealthRepo } } + private static void ValidateRegistrations(IEnumerable registrations) + { + // Scan the list for duplicate names to provide a better error if there are duplicates. + + StringBuilder? builder = null; + var distinctRegistrations = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var registration in registrations) + { + if (!distinctRegistrations.Add(registration.Name)) + { + builder ??= new StringBuilder("Duplicate health checks were registered with the name(s): "); + + builder.Append(registration.Name).Append(", "); + } + } + + if (builder is not null) + { + throw new ArgumentException(builder.ToString(0, builder.Length - 2), nameof(registrations)); + } + } + internal static class EventIds { public const int HealthCheckPublisherProcessingBeginId = 100; diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs b/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs index 3e2e9e89d738..b65b44cf8e8e 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 01def08ac1d6..428143d13222 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1,8 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.DependencyInjection.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index b7dcd8e6d42f..e6c1c348e1ea 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Options; using Xunit; #nullable enable @@ -36,6 +37,56 @@ private static class HealthCheckPublisherEventIds public static readonly EventId HealthCheckPublisherTimeout = new EventId(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherTimeoutId, HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherTimeoutName); } + [Fact] + public void Constructor_ThrowsUsefulExceptionForDuplicateNames() + { + // Arrange + // + // Doing this the old fashioned way so we can verify that the exception comes + // from the constructor. + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddOptions(); + serviceCollection.AddLogging(); + serviceCollection.AddHealthChecks() + .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))); + + // Choosing big values for tests to make sure that we're not dependent on the defaults. + // All of the tests that rely on the timer will set their own values for speed. + serviceCollection.Configure(options => + { + options.Delay = TimeSpan.FromMinutes(5); + options.Period = TimeSpan.FromMinutes(5); + options.Timeout = TimeSpan.FromMinutes(5); + }); + + var services = serviceCollection.BuildServiceProvider(); + + var healthCheckService = services.GetRequiredService(); + var healthCheckServiceOptions = services.GetRequiredService>(); + var healthCheckPublisherOptions = services.GetRequiredService>(); + var logger = services.GetRequiredService>(); + + var publishers = new IHealthCheckPublisher[] + { + }; + + // Act + var exception = Assert.Throws(() => new HealthCheckPublisherHostedService( + healthCheckService, + healthCheckServiceOptions, + healthCheckPublisherOptions, + logger, + publishers)); + + // Assert + Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message); + } + [Fact] public async Task StartAsync_WithoutPublishers_DoesNotStartTimer() { From e3369561f91b6200f891e3969e6549a2e6091efc Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 4 Aug 2022 20:26:37 +0100 Subject: [PATCH 55/91] Add Timeout as a per-HC property --- .../Abstractions/src/HealthCheckOptions.cs | 18 ++++++++++++++---- .../Abstractions/src/PublicAPI.Unshipped.txt | 3 ++- .../src/HealthCheckPublisherHostedService.cs | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs b/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs index 99678ecc4747..dd897b559f6f 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs @@ -17,10 +17,12 @@ public class HealthCheckOptions : IEquatable /// /// An optional representing the initial delay applied after the application starts before executing the check. /// An optional representing the individual period of the check. - public HealthCheckOptions(TimeSpan? delay = default, TimeSpan? period = default) + /// An optional representing the individual timeout of the check. + public HealthCheckOptions(TimeSpan? delay = default, TimeSpan? period = default, TimeSpan? timeout = default) { Delay = delay; Period = period; + Timeout = timeout; } /// @@ -32,10 +34,16 @@ public HealthCheckOptions(TimeSpan? delay = default, TimeSpan? period = default) public TimeSpan? Delay { get; } /// - /// Gets or sets the individual period used for the check. + /// Gets the individual period used for the check. /// public TimeSpan? Period { get; } + /// + /// Gets the timeout for executing the health check. + /// Use to execute with no timeout. + /// + public TimeSpan? Timeout { get; } + /// public override bool Equals(object? obj) { @@ -47,15 +55,17 @@ public bool Equals(HealthCheckOptions? other) { return other is not null && EqualityComparer.Default.Equals(Delay, other.Delay) && - EqualityComparer.Default.Equals(Period, other.Period); + EqualityComparer.Default.Equals(Period, other.Period) && + EqualityComparer.Default.Equals(Timeout, other.Timeout); } /// public override int GetHashCode() { - var hashCode = -1368980644; + var hashCode = 251827172; hashCode = hashCode * -1521134295 + Delay.GetHashCode(); hashCode = hashCode * -1521134295 + Period.GetHashCode(); + hashCode = hashCode * -1521134295 + Timeout.GetHashCode(); return hashCode; } } diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 485ccdf0994e..a096888e877b 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -2,8 +2,9 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Delay.get -> System.TimeSpan? Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Equals(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? other) -> bool -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.HealthCheckOptions(System.TimeSpan? delay = null, System.TimeSpan? period = null) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.HealthCheckOptions(System.TimeSpan? delay = null, System.TimeSpan? period = null, System.TimeSpan? timeout = null) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Period.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Timeout.get -> System.TimeSpan? Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Options.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 51cef14671ee..335dba4928dd 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -168,7 +168,7 @@ internal async Task RunAsync(HealthCheckOptions? healthCheckOptions = default) CancellationTokenSource? cancellation = null; try { - var timeout = _healthCheckPublisherOptions.Value.Timeout; + var timeout = healthCheckOptions.Timeout ?? _healthCheckPublisherOptions.Value.Timeout; cancellation = CancellationTokenSource.CreateLinkedTokenSource(_stopping.Token); _runTokenSource = cancellation; From 19d28046e00bf1d487fad51aaed64be3a36620eb Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 4 Aug 2022 20:39:14 +0100 Subject: [PATCH 56/91] Remove no longer used HealthReportEntryDictionary --- .../src/HealthReportEntryDictionary.cs | 81 ---------- .../test/HealthReportEntryDictionaryTest.cs | 147 ------------------ 2 files changed, 228 deletions(-) delete mode 100644 src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs delete mode 100644 src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs diff --git a/src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs b/src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs deleted file mode 100644 index 6ce6a9084fe4..000000000000 --- a/src/HealthChecks/HealthChecks/src/HealthReportEntryDictionary.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represents a collection of ordered by keys. -/// -internal sealed class HealthReportEntryDictionary : IReadOnlyDictionary -{ - // We make the HealthReportEntryDictionary implements IReadOnlyDictionary, so to have a Dictionary interface to be used by the HealthReport cctor accepting duplicate keys - private readonly Dictionary> _entries; - - /// - /// Returns the first corresponding to the given key - /// - /// - /// The first corresponding to the given key - public HealthReportEntry this[string key] => _entries[key].FirstOrDefault(); - - /// - public IEnumerable Keys => _entries.Keys; - - /// - public IEnumerable Values => _entries.SelectMany(e => e.Value).AsEnumerable(); - - /// - public int Count => _entries.Count; - - /// - /// Create a starting from a of - /// - /// An of - /// An to compare keys. - public HealthReportEntryDictionary(ICollection> entries, StringComparer? stringComparer = default) - { - stringComparer ??= StringComparer.OrdinalIgnoreCase; - _entries = entries.GroupBy(e => e.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Select(s => s.Value).ToList(), stringComparer); - } - - /// - public bool ContainsKey(string key) - { - return _entries.ContainsKey(key); ; - } - - /// - public IEnumerator> GetEnumerator() - { - return _entries.SelectMany(kvp => kvp.Value.Select(v => new KeyValuePair(kvp.Key, v))).GetEnumerator(); - } - - /// - public bool TryGetValue(string key, out HealthReportEntry value) - { - foreach (var entry in _entries) - { - if (entry.Key.Equals(key, StringComparison.Ordinal)) - { - value = entry.Value.First(); - return true; - } - } - - value = default; - return false; - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } -} diff --git a/src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs b/src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs deleted file mode 100644 index dd3d754c6b22..000000000000 --- a/src/HealthChecks/HealthChecks/test/HealthReportEntryDictionaryTest.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using Xunit; - -#nullable enable - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -public class HealthReportEntryDictionaryTest -{ - [Fact] - public void Constructor_GenerateDictionaryWithDuplicates() - { - // Arrange - // Act - var result = new HealthReportEntryDictionary(new List>() - { - new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - }); - - // Assert - Assert.Collection( - result.OrderBy(kvp => kvp.Key), - actual => - { - Assert.Equal("Bar", actual.Key); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); - }, - actual => - { - Assert.Equal("Foo", actual.Key); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); - }, - actual => - { - Assert.Equal("Foo", actual.Key); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); - }, - actual => - { - Assert.Equal("Quack", actual.Key); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); - }, - actual => - { - Assert.Equal("Quick", actual.Key); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); - }, - actual => - { - Assert.Equal("Quick", actual.Key); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); - }, - actual => - { - Assert.Equal("Quick", actual.Key); - Assert.Equal(HealthStatus.Healthy, actual.Value.Status); - Assert.Equal(actual.Value.Duration, TimeSpan.MinValue); - }); - } - - [Fact] - public void Indexer() - { - // Arrange - var first = new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null)); - var healthReportEntryDictionary = new HealthReportEntryDictionary(new List>() - { - first, - new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - }); - - // Act - var result = healthReportEntryDictionary["Foo"]; - - // Assert - Assert.Equal(result, first.Value); - } - - [Fact] - public void ContainsKey() - { - // Arrange - var healthReportEntryDictionary = new HealthReportEntryDictionary(new List>() - { - new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - }); - - // Act - var result = healthReportEntryDictionary.ContainsKey("Foo"); - - // Assert - Assert.True(result); - } - - [Fact] - public void TryGetValue() - { - // Arrange - var first = new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null)); - var healthReportEntryDictionary = new HealthReportEntryDictionary(new List>() - { - first, - new KeyValuePair("Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Bar", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quick", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - new KeyValuePair("Quack", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null)), - }); - - // Act - var result = healthReportEntryDictionary.TryGetValue("Foo", out var resultOut); - - // Assert - Assert.True(result); - Assert.Equal(resultOut, first.Value); - } - -} From 919f3bcb7c0367262f40090254af9a1c108edc18 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 19 Oct 2022 22:58:15 +0100 Subject: [PATCH 57/91] Rename HealthCheckRegistrationParameters --- .../Abstractions/src/HealthCheckOptions.cs | 71 ------------------- .../src/HealthCheckRegistration.cs | 16 ++--- .../src/HealthCheckRegistrationParameters.cs | 51 +++++++++++++ .../Abstractions/src/PublicAPI.Unshipped.txt | 21 +++--- .../HealthChecksBuilderAddCheckExtensions.cs | 24 +++---- .../HealthChecksBuilderDelegateExtensions.cs | 24 +++---- .../HealthChecks/src/PublicAPI.Unshipped.txt | 14 ++-- 7 files changed, 100 insertions(+), 121 deletions(-) delete mode 100644 src/HealthChecks/Abstractions/src/HealthCheckOptions.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs diff --git a/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs b/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs deleted file mode 100644 index dd897b559f6f..000000000000 --- a/src/HealthChecks/Abstractions/src/HealthCheckOptions.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represent the individual health check options associated with an . -/// -public class HealthCheckOptions : IEquatable -{ - - /// - /// Creates a new . - /// - /// An optional representing the initial delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// An optional representing the individual timeout of the check. - public HealthCheckOptions(TimeSpan? delay = default, TimeSpan? period = default, TimeSpan? timeout = default) - { - Delay = delay; - Period = period; - Timeout = timeout; - } - - /// - /// Gets the initial individual delay applied to the - /// individual HealthCheck after the application starts before executing. - /// The delay is applied once at startup, and does - /// not apply to subsequent iterations. - /// - public TimeSpan? Delay { get; } - - /// - /// Gets the individual period used for the check. - /// - public TimeSpan? Period { get; } - - /// - /// Gets the timeout for executing the health check. - /// Use to execute with no timeout. - /// - public TimeSpan? Timeout { get; } - - /// - public override bool Equals(object? obj) - { - return Equals(obj as HealthCheckOptions); - } - - /// - public bool Equals(HealthCheckOptions? other) - { - return other is not null && - EqualityComparer.Default.Equals(Delay, other.Delay) && - EqualityComparer.Default.Equals(Period, other.Period) && - EqualityComparer.Default.Equals(Timeout, other.Timeout); - } - - /// - public override int GetHashCode() - { - var hashCode = 251827172; - hashCode = hashCode * -1521134295 + Delay.GetHashCode(); - hashCode = hashCode * -1521134295 + Period.GetHashCode(); - hashCode = hashCode * -1521134295 + Timeout.GetHashCode(); - return hashCode; - } -} diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index 0a00d4e1f712..0f514ca27691 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -68,8 +68,8 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckOptions? options) + /// An optional representing the individual health check parameters. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckRegistrationParameters? parameters) { if (name == null) { @@ -91,7 +91,7 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = (_) => instance; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Options = options; + Parameters = parameters; } /// @@ -145,14 +145,14 @@ public HealthCheckRegistration( /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check parameters. public HealthCheckRegistration( string name, Func factory, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, - HealthCheckOptions? options) + HealthCheckRegistrationParameters? parameters) { if (name == null) { @@ -174,7 +174,7 @@ public HealthCheckRegistration( Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = factory; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Options = options; + Parameters = parameters; } /// @@ -217,9 +217,9 @@ public TimeSpan Timeout } /// - /// Gets the individual options associated with a health check. + /// Gets the individual parameters associated with a health check. /// - public HealthCheckOptions? Options { get; } + public HealthCheckRegistrationParameters? Parameters { get; } /// /// Gets or sets the health check name. diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs new file mode 100644 index 000000000000..5f076598fb62 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represent the individual health check parameters associated with an . +/// +public class HealthCheckRegistrationParameters +{ + /// + /// Creates a new . + /// + /// An optional representing the individual delay applied after the application starts before executing the check. + /// An optional representing the individual period of the check. + /// An optional representing the individual timeout of the check. + /// An optional representing whether the health check should be run. + public HealthCheckRegistrationParameters(TimeSpan? delay = default, TimeSpan? period = default, TimeSpan? timeout = default, bool isEnabled = true) + { + Delay = delay; + Period = period; + Timeout = timeout; + IsEnabled = isEnabled; + } + + /// + /// Gets or sets the individual delay applied to the health check after the application starts before executing + /// instances. The delay is applied once at startup, and does + /// not apply to subsequent iterations. + /// + public TimeSpan? Delay { get; } + + /// + /// Gets the individual period used for the check. + /// + public TimeSpan? Period { get; } + + /// + /// Gets the individual timeout for executing the health check. + /// Use to execute with no timeout. + /// + public TimeSpan? Timeout { get; } + + /// + /// Gets or sets whether the health check should be run. Enabled by default. + /// + public bool IsEnabled { get; set; } +} diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index a096888e877b..9cc16388d100 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1,12 +1,11 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Delay.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Equals(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? other) -> bool -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.HealthCheckOptions(System.TimeSpan? delay = null, System.TimeSpan? period = null, System.TimeSpan? timeout = null) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Period.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Timeout.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Options.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? -override Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.Equals(object? obj) -> bool -override Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions.GetHashCode() -> int +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Parameters.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Delay.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.HealthCheckRegistrationParameters(System.TimeSpan? delay = null, System.TimeSpan? period = null, System.TimeSpan? timeout = null, bool isEnabled = true) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.get -> bool +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Period.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Timeout.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 99df1e8044f9..baf284d59d9b 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -72,7 +72,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -82,7 +82,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckOptions? options = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -99,7 +99,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, parameters)); } /// @@ -171,7 +171,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -186,7 +186,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckOptions? options = default) where T : class, IHealthCheck + HealthCheckRegistrationParameters? parameters = default) where T : class, IHealthCheck { if (builder == null) { @@ -198,7 +198,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, parameters)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] @@ -251,7 +251,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< HealthStatus? failureStatus, params object[] args) where T : class, IHealthCheck { - return AddTypeActivatedCheck(builder, name, failureStatus, tags: default, timeout: default, options: default, args); + return AddTypeActivatedCheck(builder, name, failureStatus, tags: default, timeout: default, parameters: default, args); } /// @@ -279,7 +279,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< IEnumerable? tags, params object[] args) where T : class, IHealthCheck { - return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: default, options: default, args); + return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: default, parameters: default, args); } /// @@ -309,7 +309,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< TimeSpan? timeout, params object[] args) where T : class, IHealthCheck { - return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: timeout, options: default, args); + return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: timeout, parameters: default, args); } /// @@ -325,7 +325,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. /// A representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -338,7 +338,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, - HealthCheckOptions? options, + HealthCheckRegistrationParameters? parameters, params object[] args) where T : class, IHealthCheck { if (builder == null) @@ -351,7 +351,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, parameters)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index d14485e182af..6bd33563acea 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -60,7 +60,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -69,7 +69,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckOptions? options = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -87,7 +87,7 @@ public static IHealthChecksBuilder AddCheck( } var instance = new DelegateHealthCheck((ct) => new ValueTask(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } /// @@ -135,7 +135,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -144,7 +144,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckOptions? options = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -162,7 +162,7 @@ public static IHealthChecksBuilder AddCheck( } var instance = new DelegateHealthCheck((ct) => new ValueTask(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } /// @@ -210,7 +210,7 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( @@ -219,7 +219,7 @@ public static IHealthChecksBuilder AddAsyncCheck( Func> check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckOptions? options = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -237,7 +237,7 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } /// @@ -285,7 +285,7 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( @@ -294,7 +294,7 @@ public static IHealthChecksBuilder AddAsyncCheck( Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, - HealthCheckOptions? options = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -312,6 +312,6 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, options)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } } diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 428143d13222..05ac9beac70f 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1,8 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckOptions? options = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From 75bbaa6bfd08ed459c72ff54d91829d56f2b1fe9 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 19 Oct 2022 23:00:22 +0100 Subject: [PATCH 58/91] Align PublisherHostedService with OMEX implementation --- .../src/HealthCheckPublisherHostedService.cs | 75 +++++++++++-------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 335dba4928dd..dcbc8747a4cb 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -22,9 +22,9 @@ internal sealed partial class HealthCheckPublisherHostedService : IHostedService private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; - private readonly HealthCheckOptions _defaultHealthCheckOptions; - private readonly Dictionary> _healthChecksByOptions; - private Dictionary? _timersByOptions; + private readonly (TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) _defaultTimerOptions; + private readonly Dictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), List> _healthChecksByOptions; + private Dictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer>? _timersByOptions; private readonly CancellationTokenSource _stopping; private CancellationTokenSource? _runTokenSource; @@ -74,11 +74,16 @@ public HealthCheckPublisherHostedService( // actually tries to **run** health checks would be real baaaaad. ValidateRegistrations(_healthCheckServiceOptions.Value.Registrations); - _defaultHealthCheckOptions = new HealthCheckOptions(_healthCheckPublisherOptions.Value.Delay, _healthCheckPublisherOptions.Value.Period); - // Group healthcheck registrations by delay and period, to build a Dictionary<(TimeSpan, TimeSpan), List> - // For HCs with no Delay or Period, we default to the publisher values - _healthChecksByOptions = (from r in _healthCheckServiceOptions.Value.Registrations - group r by r.Options ?? _defaultHealthCheckOptions).ToDictionary(g => g.Key, g => g.ToList()); + _defaultTimerOptions = (_healthCheckPublisherOptions.Value.Delay, _healthCheckPublisherOptions.Value.Period, _healthCheckPublisherOptions.Value.Timeout); + + // Group healthcheck registrations by Delay, Period and Timeout, to build a Dictionary<(TimeSpan, TimeSpan, TimeSpan), List> + // For HCs with no Delay, Period or Timeout, we default to the publisher values + _healthChecksByOptions = _healthCheckServiceOptions.Value.Registrations.GroupBy(r => GetTimerOptionsOrDefault(r)).ToDictionary(g => g.Key, g => g.ToList()); + } + + private (TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) GetTimerOptionsOrDefault(HealthCheckRegistration registration) + { + return (registration.Parameters?.Delay ?? _healthCheckPublisherOptions.Value.Delay, registration.Parameters?.Period ?? _healthCheckPublisherOptions.Value.Period, registration.Parameters?.Timeout ?? _healthCheckPublisherOptions.Value.Timeout); } internal bool IsStopping => _stopping.IsCancellationRequested; @@ -92,7 +97,7 @@ public Task StartAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - // IMPORTANT - make sure this is the last thing that happens in this method. The timer can + // IMPORTANT - make sure this is the last thing that happens in this method. The timers can // fire before other code runs. _timersByOptions = CreateTimers(_healthChecksByOptions); @@ -128,23 +133,23 @@ public Task StopAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - private Dictionary CreateTimers(IReadOnlyDictionary> periodHealthChecksMap) + private Dictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer> CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), List> healthChecksByOptions) { - return periodHealthChecksMap.Select(m => CreateTimer(m.Key)).ToDictionary(kv => kv.Key, kv => kv.Value); + return healthChecksByOptions.Select(m => CreateTimer(m.Key)).ToDictionary(kv => kv.Key, kv => kv.Value); } - private KeyValuePair CreateTimer(HealthCheckOptions healthCheckOptions) + private KeyValuePair<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer> CreateTimer((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) timerOptions) { - return new KeyValuePair( - healthCheckOptions, + return new KeyValuePair<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer>( + timerOptions, NonCapturingTimer.Create( async (state) => { - await RunAsync(healthCheckOptions: healthCheckOptions).ConfigureAwait(false); + await RunAsync(timerOptions).ConfigureAwait(false); }, null, - dueTime: healthCheckOptions.Delay ?? _healthCheckPublisherOptions.Value.Delay, // Default to publisher Delay - period: healthCheckOptions.Period ?? _healthCheckPublisherOptions.Value.Period) // Default to publisher Period + dueTime: timerOptions.Delay, + period: timerOptions.Period) ); } @@ -155,12 +160,9 @@ internal void CancelToken() } // Internal for testing - internal async Task RunAsync(HealthCheckOptions? healthCheckOptions = default) + internal async Task RunAsync((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout)? timerOptions = null) { - if (healthCheckOptions == default) - { - healthCheckOptions = _defaultHealthCheckOptions; - } + timerOptions ??= _defaultTimerOptions; var duration = ValueStopwatch.StartNew(); Logger.HealthCheckPublisherProcessingBegin(_logger); @@ -168,13 +170,13 @@ internal async Task RunAsync(HealthCheckOptions? healthCheckOptions = default) CancellationTokenSource? cancellation = null; try { - var timeout = healthCheckOptions.Timeout ?? _healthCheckPublisherOptions.Value.Timeout; + var timeout = timerOptions.Value.Timeout; cancellation = CancellationTokenSource.CreateLinkedTokenSource(_stopping.Token); _runTokenSource = cancellation; cancellation.CancelAfter(timeout); - await RunAsyncCore(healthCheckOptions, cancellation.Token).ConfigureAwait(false); + await RunAsyncCore(timerOptions.Value, cancellation.Token).ConfigureAwait(false); Logger.HealthCheckPublisherProcessingEnd(_logger, duration.GetElapsedTime()); } @@ -194,22 +196,35 @@ internal async Task RunAsync(HealthCheckOptions? healthCheckOptions = default) } } - private async Task RunAsyncCore(HealthCheckOptions healthCheckOptions, CancellationToken cancellationToken) + private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) timerOptions, CancellationToken cancellationToken) { // Forcibly yield - we want to unblock the timer thread. await Task.Yield(); - // Concatenate predicates - we only run HCs with the set delay and period + // Concatenate predicates - we only run HCs at the set delay, period and timeout, and that are enabled var withOptionsPredicate = (HealthCheckRegistration r) => { - var hasOptions = (r.Options == default && healthCheckOptions == _defaultHealthCheckOptions) || - r.Options == healthCheckOptions; + // Check whether the current timer options correspond to the ones of the HC registration + var rOptions = GetTimerOptionsOrDefault(r); + var hasOptions = rOptions == timerOptions; + if (!hasOptions) + { + return false; + } + + // Check if HC is enabled + if (r.Parameters != null && !r.Parameters.IsEnabled) + { + return false; + } + if (_healthCheckPublisherOptions?.Value.Predicate == null) { - return hasOptions; + return true; } - return hasOptions && _healthCheckPublisherOptions.Value.Predicate(r); + // Else check the user-applied predicates + return _healthCheckPublisherOptions.Value.Predicate(r); }; // The health checks service does it's own logging, and doesn't throw exceptions. From 09af844fb448497c06db5c8cd5aa5aa2bc64cad0 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 19 Oct 2022 23:01:27 +0100 Subject: [PATCH 59/91] Add remaining tests for multiple periods and selectability --- .../HealthCheckPublisherHostedServiceTest.cs | 191 +++++++++++++++++- 1 file changed, 183 insertions(+), 8 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index e6c1c348e1ea..95adda6e15c6 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -157,7 +157,7 @@ public async Task StartAsync_WithPublishers_StartsTimer_RunsPublishers() new TestPublisher() { Wait = unblock2.Task, }, }; - var service = CreateService(publishers, configure: (options) => + var service = CreateService(publishers, configurePublisherOptions: (options) => { options.Delay = TimeSpan.FromMilliseconds(0); }); @@ -290,6 +290,90 @@ public async Task RunAsync_WaitsForCompletion_Single() entry => { Assert.Equal(HealthCheckPublisherEventIds.HealthCheckPublisherProcessingEnd, entry.EventId); }); } + [Fact] + public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() + { + // Arrange + const string HealthyMessage = "Everything is A-OK"; + + var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var unblockDelayedCheck = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var publishers = new TestPublisher[] + { + new TestPublisher() { Wait = unblock.Task, }, + }; + + var service = CreateService(publishers, configureBuilder: b => + { + b.AddAsyncCheck("CheckDefault", _ => + { + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }); + + b.AddAsyncCheck("CheckDelay2Period18", _ => + { + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); + + b.AddAsyncCheck("CheckDelay7Period11", _ => + { + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); + + b.AddAsyncCheck("CheckDelay9Period5", _ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); + + b.AddAsyncCheck("DisabledCheck", _ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); + }); + + try + { + await service.StartAsync(); + + // Act + var running = service.RunAsync(); + + await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + + unblock.SetResult(null); + + await running.TimeoutAfter(TimeSpan.FromSeconds(20)); + + await Task.WhenAll(unblock.Task, unblockDelayedCheck.Task); + + // Assert + Assert.True(service.IsTimerRunning); + Assert.False(service.IsStopping); + + for (var i = 0; i < publishers.Length; i++) + { + Assert.True(publishers[i].Entries.Count == 4); + var entries = publishers[i].Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); + Assert.Equal( + new[] { "CheckDefault", "CheckDelay2Period18", "CheckDelay7Period11", "CheckDelay9Period5" }, + entries); + } + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + // Not testing logs here to avoid differences in logging order [Fact] public async Task RunAsync_WaitsForCompletion_Multiple() @@ -409,7 +493,7 @@ public async Task RunAsync_CanFilterHealthChecks() new TestPublisher(), }; - var service = CreateService(publishers, configure: (options) => + var service = CreateService(publishers, configurePublisherOptions: (options) => { options.Predicate = (r) => r.Name == "one"; }); @@ -436,6 +520,88 @@ public async Task RunAsync_CanFilterHealthChecks() } } + [Fact] + public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() + { + // Arrange + const string HealthyMessage = "Everything is A-OK"; + + var unblockDelayedCheck = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var publishers = new TestPublisher[] + { + new TestPublisher(), + new TestPublisher(), + }; + + var service = CreateService( + publishers, + configurePublisherOptions: (options) => + { + options.Predicate = (r) => r.Name.Contains("Delay"); + }, + configureBuilder: b => + { + b.AddAsyncCheck("CheckDefault", _ => + { + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }); + + b.AddAsyncCheck("CheckDelay2Period18", _ => + { + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); + + b.AddAsyncCheck("CheckDelay7Period11", _ => + { + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); + + b.AddAsyncCheck("CheckDelay9Period5", _ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); + + b.AddAsyncCheck("DisabledCheck", _ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); + }); + + try + { + await service.StartAsync(); + + // Act + await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(20)); + + await unblockDelayedCheck.Task; + + // Assert + for (var i = 0; i < publishers.Length; i++) + { + var entries = publishers[i].Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); + + Assert.True(entries.Count() == 3); + Assert.Equal( + new[] { "CheckDelay2Period18", "CheckDelay7Period11", "CheckDelay9Period5" }, + entries); + } + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + [Fact] public async Task RunAsync_HandlesExceptions() { @@ -510,15 +676,24 @@ public async Task RunAsync_HandlesExceptions_Multiple() private HealthCheckPublisherHostedService CreateService( IHealthCheckPublisher[] publishers, - Action? configure = null, + Action? configurePublisherOptions = null, + Action? configureBuilder = null, TestSink? sink = null) { var serviceCollection = new ServiceCollection(); serviceCollection.AddOptions(); serviceCollection.AddLogging(); - serviceCollection.AddHealthChecks() - .AddCheck("one", () => { return HealthCheckResult.Healthy(); }) - .AddCheck("two", () => { return HealthCheckResult.Healthy(); }); + + IHealthChecksBuilder builder = serviceCollection.AddHealthChecks(); + if (configureBuilder == null) + { + builder.AddCheck("one", () => { return HealthCheckResult.Healthy(); }) + .AddCheck("two", () => { return HealthCheckResult.Healthy(); }); + } + else + { + configureBuilder(builder); + } // Choosing big values for tests to make sure that we're not dependent on the defaults. // All of the tests that rely on the timer will set their own values for speed. @@ -537,9 +712,9 @@ private HealthCheckPublisherHostedService CreateService( } } - if (configure != null) + if (configurePublisherOptions != null) { - serviceCollection.Configure(configure); + serviceCollection.Configure(configurePublisherOptions); } if (sink != null) From 449a16f38423d90baba5e78125c5b788f2ef4e48 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Wed, 19 Oct 2022 23:14:45 +0100 Subject: [PATCH 60/91] Simplify GroupBy lambda --- .../HealthChecks/src/HealthCheckPublisherHostedService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index dcbc8747a4cb..7cfe89265535 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -78,7 +78,7 @@ public HealthCheckPublisherHostedService( // Group healthcheck registrations by Delay, Period and Timeout, to build a Dictionary<(TimeSpan, TimeSpan, TimeSpan), List> // For HCs with no Delay, Period or Timeout, we default to the publisher values - _healthChecksByOptions = _healthCheckServiceOptions.Value.Registrations.GroupBy(r => GetTimerOptionsOrDefault(r)).ToDictionary(g => g.Key, g => g.ToList()); + _healthChecksByOptions = _healthCheckServiceOptions.Value.Registrations.GroupBy(GetTimerOptionsOrDefault).ToDictionary(g => g.Key, g => g.ToList()); } private (TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) GetTimerOptionsOrDefault(HealthCheckRegistration registration) From d14ee101356bb48143723f95f6bb32ded32a8dd8 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Fri, 6 Jan 2023 16:31:42 +0000 Subject: [PATCH 61/91] Update reusing PublisherOptions in AddCheck --- .../src/HealthCheckRegistrationParameters.cs | 51 ------------------- .../HealthChecksBuilderAddCheckExtensions.cs | 12 ++--- .../HealthChecksBuilderDelegateExtensions.cs | 16 +++--- .../src/HealthCheckContext.cs | 0 .../src/HealthCheckPublisherHostedService.cs | 6 --- .../src/HealthCheckRegistration.cs | 6 +-- .../src/IHealthCheck.cs | 0 .../HealthCheckPublisherHostedServiceTest.cs | 46 ++++++++++------- 8 files changed, 45 insertions(+), 92 deletions(-) delete mode 100644 src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs rename src/HealthChecks/{Abstractions => HealthChecks}/src/HealthCheckContext.cs (100%) rename src/HealthChecks/{Abstractions => HealthChecks}/src/HealthCheckRegistration.cs (98%) rename src/HealthChecks/{Abstractions => HealthChecks}/src/IHealthCheck.cs (100%) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs deleted file mode 100644 index 5f076598fb62..000000000000 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represent the individual health check parameters associated with an . -/// -public class HealthCheckRegistrationParameters -{ - /// - /// Creates a new . - /// - /// An optional representing the individual delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// An optional representing the individual timeout of the check. - /// An optional representing whether the health check should be run. - public HealthCheckRegistrationParameters(TimeSpan? delay = default, TimeSpan? period = default, TimeSpan? timeout = default, bool isEnabled = true) - { - Delay = delay; - Period = period; - Timeout = timeout; - IsEnabled = isEnabled; - } - - /// - /// Gets or sets the individual delay applied to the health check after the application starts before executing - /// instances. The delay is applied once at startup, and does - /// not apply to subsequent iterations. - /// - public TimeSpan? Delay { get; } - - /// - /// Gets the individual period used for the check. - /// - public TimeSpan? Period { get; } - - /// - /// Gets the individual timeout for executing the health check. - /// Use to execute with no timeout. - /// - public TimeSpan? Timeout { get; } - - /// - /// Gets or sets whether the health check should be run. Enabled by default. - /// - public bool IsEnabled { get; set; } -} diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index baf284d59d9b..dc93f7a37fbe 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -72,7 +72,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -82,7 +82,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + HealthCheckPublisherOptions? parameters = default) { if (builder == null) { @@ -171,7 +171,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -186,7 +186,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) where T : class, IHealthCheck + HealthCheckPublisherOptions? parameters = default) where T : class, IHealthCheck { if (builder == null) { @@ -325,7 +325,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. /// A representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -338,7 +338,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, - HealthCheckRegistrationParameters? parameters, + HealthCheckPublisherOptions? parameters, params object[] args) where T : class, IHealthCheck { if (builder == null) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 6bd33563acea..03f2fc9e53ab 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -60,7 +60,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -69,7 +69,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + HealthCheckPublisherOptions? parameters = default) { if (builder == null) { @@ -135,7 +135,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -144,7 +144,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + HealthCheckPublisherOptions? parameters = default) { if (builder == null) { @@ -210,7 +210,7 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( @@ -219,7 +219,7 @@ public static IHealthChecksBuilder AddAsyncCheck( Func> check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + HealthCheckPublisherOptions? parameters = default) { if (builder == null) { @@ -285,7 +285,7 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( @@ -294,7 +294,7 @@ public static IHealthChecksBuilder AddAsyncCheck( Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + HealthCheckPublisherOptions? parameters = default) { if (builder == null) { diff --git a/src/HealthChecks/Abstractions/src/HealthCheckContext.cs b/src/HealthChecks/HealthChecks/src/HealthCheckContext.cs similarity index 100% rename from src/HealthChecks/Abstractions/src/HealthCheckContext.cs rename to src/HealthChecks/HealthChecks/src/HealthCheckContext.cs diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 7cfe89265535..979ba0b86dae 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -212,12 +212,6 @@ private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period, TimeSpan Timeo return false; } - // Check if HC is enabled - if (r.Parameters != null && !r.Parameters.IsEnabled) - { - return false; - } - if (_healthCheckPublisherOptions?.Value.Predicate == null) { return true; diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs similarity index 98% rename from src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs rename to src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs index 0f514ca27691..eead8979b762 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs @@ -69,7 +69,7 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. /// An optional representing the individual health check parameters. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckRegistrationParameters? parameters) + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckPublisherOptions? parameters) { if (name == null) { @@ -152,7 +152,7 @@ public HealthCheckRegistration( HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, - HealthCheckRegistrationParameters? parameters) + HealthCheckPublisherOptions? parameters) { if (name == null) { @@ -219,7 +219,7 @@ public TimeSpan Timeout /// /// Gets the individual parameters associated with a health check. /// - public HealthCheckRegistrationParameters? Parameters { get; } + public HealthCheckPublisherOptions? Parameters { get; } /// /// Gets or sets the health check name. diff --git a/src/HealthChecks/Abstractions/src/IHealthCheck.cs b/src/HealthChecks/HealthChecks/src/IHealthCheck.cs similarity index 100% rename from src/HealthChecks/Abstractions/src/IHealthCheck.cs rename to src/HealthChecks/HealthChecks/src/IHealthCheck.cs diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 95adda6e15c6..e3403f906d6f 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -315,27 +315,32 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() { return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); + parameters: new() + { + Delay = TimeSpan.FromSeconds(2), + Period = TimeSpan.FromSeconds(18) + }); b.AddAsyncCheck("CheckDelay7Period11", _ => { return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); + parameters: new() + { + Delay = TimeSpan.FromSeconds(7), + Period = TimeSpan.FromSeconds(11) + }); b.AddAsyncCheck("CheckDelay9Period5", _ => { unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); - - b.AddAsyncCheck("DisabledCheck", _ => + parameters: new() { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); + Delay = TimeSpan.FromSeconds(9), + Period = TimeSpan.FromSeconds(5) + }); }); try @@ -551,27 +556,32 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() { return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); + parameters: new() + { + Delay = TimeSpan.FromSeconds(2), + Period = TimeSpan.FromSeconds(18) + }); b.AddAsyncCheck("CheckDelay7Period11", _ => { return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); + parameters: new() + { + Delay = TimeSpan.FromSeconds(7), + Period = TimeSpan.FromSeconds(11) + }); b.AddAsyncCheck("CheckDelay9Period5", _ => { unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); - - b.AddAsyncCheck("DisabledCheck", _ => + parameters: new() { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); + Delay = TimeSpan.FromSeconds(9), + Period = TimeSpan.FromSeconds(5) + }); }); try From d3bde27ee359a40df8293457ae24d35c793f6a3a Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Fri, 6 Jan 2023 16:35:38 +0000 Subject: [PATCH 62/91] Move HealthCheckRegistration, HealthCheckContext, IHealthCheck API definition --- .../Abstractions/src/PublicAPI.Shipped.txt | 20 ------------------- .../HealthChecks/src/PublicAPI.Shipped.txt | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt index 6d8294f838d6..ce75852dec61 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt @@ -1,22 +1,4 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.HealthCheckContext() -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.get -> System.Func! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.get -> string! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Tags.get -> System.Collections.Generic.ISet! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.set -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.HealthCheckResult() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Data.get -> System.Collections.Generic.IReadOnlyDictionary! @@ -44,8 +26,6 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Degraded = 1 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy = 2 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy = 0 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck.CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext! context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher.PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport! report, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Degraded(string? description = null, System.Exception? exception = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index 2d8da1fe23c5..4bda96b00f8c 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -1,4 +1,24 @@ #nullable enable +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.HealthCheckContext() -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.get -> System.Func! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.get -> string! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Tags.get -> System.Collections.Generic.ISet! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck.CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext! context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! abstract Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.CheckHealthAsync(System.Func? predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions From 53f9a72b120ead3919ba0763f587598efc4cfa77 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Fri, 6 Jan 2023 16:39:41 +0000 Subject: [PATCH 63/91] Align API contract for HealthCheckPublisherOptions --- .../HealthChecks/src/PublicAPI.Unshipped.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 05ac9beac70f..292314ccabf4 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1,8 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From d5fe2ba800b2b53b592a92f6d45f167a49b6858e Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 8 Jan 2023 19:39:28 +0000 Subject: [PATCH 64/91] Restore Task APIs --- .../HealthChecks/src/DelegateHealthCheck.cs | 6 ++--- .../HealthChecksBuilderDelegateExtensions.cs | 16 ++++++------ .../test/DefaultHealthCheckServiceTest.cs | 18 ++++++------- .../HealthChecksBuilderTest.cs | 10 +++---- .../HealthCheckPublisherHostedServiceTest.cs | 26 +++++++++---------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs index ce28407dad08..ac7b82a0f80d 100644 --- a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs +++ b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs @@ -13,13 +13,13 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// internal sealed class DelegateHealthCheck : IHealthCheck { - private readonly Func> _check; + private readonly Func> _check; /// /// Create an instance of from the specified delegate. /// /// A delegate which provides the code to execute when the health check is run. - public DelegateHealthCheck(Func> check) + public DelegateHealthCheck(Func> check) { _check = check ?? throw new ArgumentNullException(nameof(check)); } @@ -30,5 +30,5 @@ public DelegateHealthCheck(Func> /// A context object associated with the current execution. /// A that can be used to cancel the health check. /// A that completes when the health check has finished, yielding the status of the component being checked. - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken).AsTask(); + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken); } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 03f2fc9e53ab..da2e9bf053ab 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -86,7 +86,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => new ValueTask(check())); + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } @@ -161,7 +161,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => new ValueTask(check(ct))); + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } @@ -177,7 +177,7 @@ public static IHealthChecksBuilder AddCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags) { return AddAsyncCheck(builder, name, check, tags, default, default); @@ -195,7 +195,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags, TimeSpan? timeout) { @@ -216,7 +216,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags = default, TimeSpan? timeout = default, HealthCheckPublisherOptions? parameters = default) @@ -252,7 +252,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags) { return AddAsyncCheck(builder, name, check, tags, default, default); @@ -270,7 +270,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags, TimeSpan? timeout) { @@ -291,7 +291,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, HealthCheckPublisherOptions? parameters = default) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 7693aa6f404f..16b38db90b84 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -41,9 +41,9 @@ public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); - b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); }); // Act @@ -144,9 +144,9 @@ public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data))); - b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage))); - b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); }); // Act @@ -250,8 +250,8 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu var service = CreateHealthChecksService(b => { b.AddAsyncCheck("Throws", ct => throw thrownException); - b.AddAsyncCheck("Faults", ct => new ValueTask(Task.FromException(faultedException))); - b.AddAsyncCheck("Succeeds", ct => new ValueTask(HealthCheckResult.Healthy())); + b.AddAsyncCheck("Faults", ct => Task.FromException(faultedException)); + b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy())); }); // Act @@ -301,7 +301,7 @@ public async Task CheckHealthAsync_SetsUpALoggerScopeForEachCheck() Assert.Equal("TestScope", item.Value); }); }); - return new ValueTask(HealthCheckResult.Healthy()); + return Task.FromResult(HealthCheckResult.Healthy()); }); var loggerFactory = new TestLoggerFactory(sink, enabled: true); diff --git a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs index b76c169bae3b..048ca2547dfd 100644 --- a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs +++ b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs @@ -20,7 +20,7 @@ public void AddCheck_Instance() // Arrange var instance = new DelegateHealthCheck((_) => { - return new ValueTask(HealthCheckResult.Healthy()); + return Task.FromResult(HealthCheckResult.Healthy()); }); var services = CreateServices(); @@ -160,7 +160,7 @@ public void AddAsyncDelegateCheck_NoArg() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", () => { - return new ValueTask(HealthCheckResult.Healthy()); + return Task.FromResult(HealthCheckResult.Healthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -183,7 +183,7 @@ public void AddAsyncDelegateCheck_CancellationToken() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", (_) => { - return new ValueTask(HealthCheckResult.Unhealthy()); + return Task.FromResult(HealthCheckResult.Unhealthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -205,10 +205,10 @@ public void ChecksCanBeRegisteredInMultipleCallsToAddHealthChecks() var services = new ServiceCollection(); services .AddHealthChecks() - .AddAsyncCheck("Foo", () => new ValueTask(HealthCheckResult.Healthy())); + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy())); services .AddHealthChecks() - .AddAsyncCheck("Bar", () => new ValueTask(HealthCheckResult.Healthy())); + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy())); // Act var options = services.BuildServiceProvider().GetRequiredService>(); diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index e3403f906d6f..dfe8fc7d0d83 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -49,11 +49,11 @@ public void Constructor_ThrowsUsefulExceptionForDuplicateNames() serviceCollection.AddOptions(); serviceCollection.AddLogging(); serviceCollection.AddHealthChecks() - .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Bar", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))); + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); // Choosing big values for tests to make sure that we're not dependent on the defaults. // All of the tests that rely on the timer will set their own values for speed. @@ -308,12 +308,12 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() { b.AddAsyncCheck("CheckDefault", _ => { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }); b.AddAsyncCheck("CheckDelay2Period18", _ => { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }, parameters: new() { @@ -323,7 +323,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() b.AddAsyncCheck("CheckDelay7Period11", _ => { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }, parameters: new() { @@ -334,7 +334,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() b.AddAsyncCheck("CheckDelay9Period5", _ => { unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }, parameters: new() { @@ -549,12 +549,12 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() { b.AddAsyncCheck("CheckDefault", _ => { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }); b.AddAsyncCheck("CheckDelay2Period18", _ => { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }, parameters: new() { @@ -564,7 +564,7 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() b.AddAsyncCheck("CheckDelay7Period11", _ => { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }, parameters: new() { @@ -575,7 +575,7 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() b.AddAsyncCheck("CheckDelay9Period5", _ => { unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }, parameters: new() { From 61376e1a4fc27fd468cf8041b179d9f3c5eb1030 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 8 Jan 2023 19:57:25 +0000 Subject: [PATCH 65/91] Move HC Report, HC Publisher to HealthChecks/src/ --- .../{Abstractions => HealthChecks}/src/HealthReport.cs | 0 .../{Abstractions => HealthChecks}/src/HealthReportEntry.cs | 0 .../{Abstractions => HealthChecks}/src/IHealthCheckPublisher.cs | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/HealthChecks/{Abstractions => HealthChecks}/src/HealthReport.cs (100%) rename src/HealthChecks/{Abstractions => HealthChecks}/src/HealthReportEntry.cs (100%) rename src/HealthChecks/{Abstractions => HealthChecks}/src/IHealthCheckPublisher.cs (100%) diff --git a/src/HealthChecks/Abstractions/src/HealthReport.cs b/src/HealthChecks/HealthChecks/src/HealthReport.cs similarity index 100% rename from src/HealthChecks/Abstractions/src/HealthReport.cs rename to src/HealthChecks/HealthChecks/src/HealthReport.cs diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntry.cs b/src/HealthChecks/HealthChecks/src/HealthReportEntry.cs similarity index 100% rename from src/HealthChecks/Abstractions/src/HealthReportEntry.cs rename to src/HealthChecks/HealthChecks/src/HealthReportEntry.cs diff --git a/src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs b/src/HealthChecks/HealthChecks/src/IHealthCheckPublisher.cs similarity index 100% rename from src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs rename to src/HealthChecks/HealthChecks/src/IHealthCheckPublisher.cs From f2585dfc1ac5429411149a61eb1dcaa3b4f95c48 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 8 Jan 2023 19:58:49 +0000 Subject: [PATCH 66/91] Align API contracts after move --- .../Abstractions/src/PublicAPI.Shipped.txt | 26 +++---------------- .../Abstractions/src/PublicAPI.Unshipped.txt | 10 ------- .../src/HealthCheckRegistration.cs | 4 +-- .../HealthChecks/src/PublicAPI.Shipped.txt | 22 +++++++++++++--- .../HealthChecks/src/PublicAPI.Unshipped.txt | 11 ++++++-- 5 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt index ce75852dec61..ca71ecca46a5 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt @@ -1,4 +1,8 @@ #nullable enable +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Degraded = 1 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy = 2 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy = 0 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.HealthCheckResult() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Data.get -> System.Collections.Generic.IReadOnlyDictionary! @@ -6,28 +10,6 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Description.get Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Exception.get -> System.Exception? Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.HealthCheckResult(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description = null, System.Exception? exception = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Entries.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, System.TimeSpan totalDuration) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, System.TimeSpan totalDuration) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.TotalDuration.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry() -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Data.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Description.get -> string? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Duration.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Exception.get -> System.Exception? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data, System.Collections.Generic.IEnumerable? tags = null) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Tags.get -> System.Collections.Generic.IEnumerable! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Degraded = 1 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy = 2 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy = 0 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher.PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport! report, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Degraded(string? description = null, System.Exception? exception = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Healthy(string? description = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Unhealthy(string? description = null, System.Exception? exception = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 9cc16388d100..7dc5c58110bf 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1,11 +1 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Parameters.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Delay.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.HealthCheckRegistrationParameters(System.TimeSpan? delay = null, System.TimeSpan? period = null, System.TimeSpan? timeout = null, bool isEnabled = true) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.get -> bool -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Period.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Timeout.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs b/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs index eead8979b762..07a57f6135f6 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs @@ -68,7 +68,7 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check parameters. + /// An optional representing the individual health check parameters. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckPublisherOptions? parameters) { if (name == null) @@ -145,7 +145,7 @@ public HealthCheckRegistration( /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check parameters. + /// An optional representing the individual health check parameters. public HealthCheckRegistration( string name, Func factory, diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index 4bda96b00f8c..321cf8d588bd 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -17,6 +17,24 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.set - Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Tags.get -> System.Collections.Generic.ISet! Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.get -> System.TimeSpan Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Entries.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, System.TimeSpan totalDuration) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, System.TimeSpan totalDuration) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.TotalDuration.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry() -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Data.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Description.get -> string? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Duration.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Exception.get -> System.Exception? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data, System.Collections.Generic.IEnumerable? tags = null) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Tags.get -> System.Collections.Generic.IEnumerable! +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher.PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport! report, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck.CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext! context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! abstract Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.CheckHealthAsync(System.Func? predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! @@ -50,10 +68,6 @@ static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExten static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 292314ccabf4..64e24c022965 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1,15 @@ #nullable enable +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Parameters.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From d2ab24e5bf88282e5a0555dc3db61512787a5648 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 15 Jan 2023 16:17:57 +0000 Subject: [PATCH 67/91] Restore 449a16f --- .../Abstractions/src/HealthCheckContext.cs | 16 ++ .../src/HealthCheckRegistration.cs | 245 ++++++++++++++++++ .../src/HealthCheckRegistrationParameters.cs | 51 ++++ .../Abstractions/src/HealthReport.cs | 81 ++++++ .../Abstractions/src/HealthReportEntry.cs | 80 ++++++ .../Abstractions/src/IHealthCheck.cs | 22 ++ .../Abstractions/src/IHealthCheckPublisher.cs | 38 +++ .../Abstractions/src/PublicAPI.Shipped.txt | 46 +++- .../Abstractions/src/PublicAPI.Unshipped.txt | 10 + .../HealthChecks/src/DelegateHealthCheck.cs | 6 +- .../HealthChecksBuilderAddCheckExtensions.cs | 12 +- .../HealthChecksBuilderDelegateExtensions.cs | 32 +-- .../src/HealthCheckPublisherHostedService.cs | 6 + .../HealthChecks/src/PublicAPI.Shipped.txt | 42 +-- .../HealthChecks/src/PublicAPI.Unshipped.txt | 21 +- .../test/DefaultHealthCheckServiceTest.cs | 18 +- .../HealthChecksBuilderTest.cs | 10 +- .../HealthCheckPublisherHostedServiceTest.cs | 72 +++-- 18 files changed, 672 insertions(+), 136 deletions(-) create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckContext.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthReport.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthReportEntry.cs create mode 100644 src/HealthChecks/Abstractions/src/IHealthCheck.cs create mode 100644 src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs diff --git a/src/HealthChecks/Abstractions/src/HealthCheckContext.cs b/src/HealthChecks/Abstractions/src/HealthCheckContext.cs new file mode 100644 index 000000000000..cef55408ef52 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckContext.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Health check context. Provides health check registrations to . +/// +public sealed class HealthCheckContext +{ + /// + /// Gets or sets the of the currently executing . + /// + // This allows null values for convenience during unit testing. This is expected to be non-null when within application code. + public HealthCheckRegistration Registration { get; set; } = default!; +} diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs new file mode 100644 index 000000000000..0f514ca27691 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -0,0 +1,245 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represent the registration information associated with an implementation. +/// +/// +/// +/// The health check registration is provided as a separate object so that application developers can customize +/// how health check implementations are configured. +/// +/// +/// The registration is provided to an implementation during execution through +/// . This allows a health check implementation to access named +/// options or perform other operations based on the registered name. +/// +/// +public sealed class HealthCheckRegistration +{ + private Func _factory; + private string _name; + private TimeSpan _timeout; + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// The instance. + /// + /// The that should be reported upon failure of the health check. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags) + : this(name, instance, failureStatus, tags, default, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// The instance. + /// + /// The that should be reported upon failure of the health check. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) + : this(name, instance, failureStatus, tags, timeout, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// The instance. + /// + /// The that should be reported upon failure of the health check. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + /// An optional representing the individual health check parameters. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckRegistrationParameters? parameters) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(timeout)); + } + + _name = name; + FailureStatus = failureStatus ?? HealthStatus.Unhealthy; + Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); + _factory = (_) => instance; + Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; + Parameters = parameters; + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// A delegate used to create the instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + public HealthCheckRegistration( + string name, + Func factory, + HealthStatus? failureStatus, + IEnumerable? tags) + : this(name, factory, failureStatus, tags, default, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// A delegate used to create the instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + public HealthCheckRegistration( + string name, + Func factory, + HealthStatus? failureStatus, + IEnumerable? tags, + TimeSpan? timeout) + : this(name, factory, failureStatus, tags, timeout, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// A delegate used to create the instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + /// An optional representing the individual health check parameters. + public HealthCheckRegistration( + string name, + Func factory, + HealthStatus? failureStatus, + IEnumerable? tags, + TimeSpan? timeout, + HealthCheckRegistrationParameters? parameters) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(timeout)); + } + + _name = name; + FailureStatus = failureStatus ?? HealthStatus.Unhealthy; + Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); + _factory = factory; + Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; + Parameters = parameters; + } + + /// + /// Gets or sets a delegate used to create the instance. + /// + public Func Factory + { + get => _factory; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _factory = value; + } + } + + /// + /// Gets or sets the that should be reported upon failure of the health check. + /// + public HealthStatus FailureStatus { get; set; } + + /// + /// Gets or sets the timeout used for the test. + /// + public TimeSpan Timeout + { + get => _timeout; + set + { + if (value <= TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _timeout = value; + } + } + + /// + /// Gets the individual parameters associated with a health check. + /// + public HealthCheckRegistrationParameters? Parameters { get; } + + /// + /// Gets or sets the health check name. + /// + public string Name + { + get => _name; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _name = value; + } + } + + /// + /// Gets a list of tags that can be used for filtering health checks. + /// + public ISet Tags { get; } +} diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs new file mode 100644 index 000000000000..5f076598fb62 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represent the individual health check parameters associated with an . +/// +public class HealthCheckRegistrationParameters +{ + /// + /// Creates a new . + /// + /// An optional representing the individual delay applied after the application starts before executing the check. + /// An optional representing the individual period of the check. + /// An optional representing the individual timeout of the check. + /// An optional representing whether the health check should be run. + public HealthCheckRegistrationParameters(TimeSpan? delay = default, TimeSpan? period = default, TimeSpan? timeout = default, bool isEnabled = true) + { + Delay = delay; + Period = period; + Timeout = timeout; + IsEnabled = isEnabled; + } + + /// + /// Gets or sets the individual delay applied to the health check after the application starts before executing + /// instances. The delay is applied once at startup, and does + /// not apply to subsequent iterations. + /// + public TimeSpan? Delay { get; } + + /// + /// Gets the individual period used for the check. + /// + public TimeSpan? Period { get; } + + /// + /// Gets the individual timeout for executing the health check. + /// Use to execute with no timeout. + /// + public TimeSpan? Timeout { get; } + + /// + /// Gets or sets whether the health check should be run. Enabled by default. + /// + public bool IsEnabled { get; set; } +} diff --git a/src/HealthChecks/Abstractions/src/HealthReport.cs b/src/HealthChecks/Abstractions/src/HealthReport.cs new file mode 100644 index 000000000000..829067fb3cfa --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthReport.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represents the result of executing a group of instances. +/// +public sealed class HealthReport +{ + /// + /// Create a new from the specified results. + /// + /// A containing the results from each health check. + /// A value indicating the time the health check service took to execute. + public HealthReport(IReadOnlyDictionary entries, TimeSpan totalDuration) + : this( + entries, + CalculateAggregateStatus(entries?.Values ?? throw new ArgumentNullException(nameof(entries))), + totalDuration) + { + } + + /// + /// Create a new from the specified results. + /// + /// A containing the results from each health check. + /// A representing the aggregate status of all the health checks. + /// A value indicating the time the health check service took to execute. + public HealthReport(IReadOnlyDictionary entries, HealthStatus status, TimeSpan totalDuration) + { + Entries = entries; + Status = status; + TotalDuration = totalDuration; + } + + /// + /// A containing the results from each health check. + /// + /// + /// The keys in this dictionary map the name of each executed health check to a for the + /// result data returned from the corresponding health check. + /// + public IReadOnlyDictionary Entries { get; } + + /// + /// Gets a representing the aggregate status of all the health checks. The value of + /// will be the most severe status reported by a health check. If no checks were executed, the value is always . + /// + public HealthStatus Status { get; } + + /// + /// Gets the time the health check service took to execute. + /// + public TimeSpan TotalDuration { get; } + + private static HealthStatus CalculateAggregateStatus(IEnumerable entries) + { + // This is basically a Min() check, but we know the possible range, so we don't need to walk the whole list + var currentValue = HealthStatus.Healthy; + foreach (var entry in entries) + { + if (currentValue > entry.Status) + { + currentValue = entry.Status; + } + + if (currentValue == HealthStatus.Unhealthy) + { + // Game over, man! Game over! + // (We hit the worst possible status, so there's no need to keep iterating) + return currentValue; + } + } + + return currentValue; + } +} diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntry.cs b/src/HealthChecks/Abstractions/src/HealthReportEntry.cs new file mode 100644 index 000000000000..3f79df0a7033 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthReportEntry.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represents an entry in a . Corresponds to the result of a single . +/// +public struct HealthReportEntry +{ + private static readonly IReadOnlyDictionary _emptyReadOnlyDictionary = new Dictionary(); + + /// + /// Creates a new with the specified values for , , + /// , and . + /// + /// A value indicating the health status of the component that was checked. + /// A human-readable description of the status of the component that was checked. + /// A value indicating the health execution duration. + /// An representing the exception that was thrown when checking for status (if any). + /// Additional key-value pairs describing the health of the component. + public HealthReportEntry(HealthStatus status, string? description, TimeSpan duration, Exception? exception, IReadOnlyDictionary? data) + : this(status, description, duration, exception, data, null) + { + } + + /// + /// Creates a new with the specified values for , , + /// , and . + /// + /// A value indicating the health status of the component that was checked. + /// A human-readable description of the status of the component that was checked. + /// A value indicating the health execution duration. + /// An representing the exception that was thrown when checking for status (if any). + /// Additional key-value pairs describing the health of the component. + /// Tags associated with the health check that generated the report entry. + public HealthReportEntry(HealthStatus status, string? description, TimeSpan duration, Exception? exception, IReadOnlyDictionary? data, IEnumerable? tags = null) + { + Status = status; + Description = description; + Duration = duration; + Exception = exception; + Data = data ?? _emptyReadOnlyDictionary; + Tags = tags ?? Enumerable.Empty(); + } + + /// + /// Gets additional key-value pairs describing the health of the component. + /// + public IReadOnlyDictionary Data { get; } + + /// + /// Gets a human-readable description of the status of the component that was checked. + /// + public string? Description { get; } + + /// + /// Gets the health check execution duration. + /// + public TimeSpan Duration { get; } + + /// + /// Gets an representing the exception that was thrown when checking for status (if any). + /// + public Exception? Exception { get; } + + /// + /// Gets the health status of the component that was checked. + /// + public HealthStatus Status { get; } + + /// + /// Gets the tags associated with the health check. + /// + public IEnumerable Tags { get; } +} diff --git a/src/HealthChecks/Abstractions/src/IHealthCheck.cs b/src/HealthChecks/Abstractions/src/IHealthCheck.cs new file mode 100644 index 000000000000..d806a7ecbe40 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/IHealthCheck.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represents a health check, which can be used to check the status of a component in the application, such as a backend service, database or some internal +/// state. +/// +public interface IHealthCheck +{ + /// + /// Runs the health check, returning the status of the component being checked. + /// + /// A context object associated with the current execution. + /// A that can be used to cancel the health check. + /// A that completes when the health check has finished, yielding the status of the component being checked. + Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default); +} diff --git a/src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs b/src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs new file mode 100644 index 000000000000..44661b347a73 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks; + +/// +/// Represents a publisher of information. +/// +/// +/// +/// The default health checks implementation provided an IHostedService implementation that can +/// be used to execute health checks at regular intervals and provide the resulting +/// data to all registered instances. +/// +/// +/// To provide an implementation, register an instance or type as a singleton +/// service in the dependency injection container. +/// +/// +/// instances are provided with a after executing +/// health checks in a background thread. The use of depend on hosting in +/// an application using IWebHost or generic host (IHost). Execution of +/// instance is not related to execution of health checks via a middleware. +/// +/// +public interface IHealthCheckPublisher +{ + /// + /// Publishes the provided . + /// + /// The . The result of executing a set of health checks. + /// The . + /// A which will complete when publishing is complete. + Task PublishAsync(HealthReport report, CancellationToken cancellationToken); +} diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt index ca71ecca46a5..6d8294f838d6 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt @@ -1,8 +1,22 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Degraded = 1 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy = 2 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy = 0 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.HealthCheckContext() -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.get -> System.Func! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.get -> string! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Tags.get -> System.Collections.Generic.ISet! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.set -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.HealthCheckResult() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Data.get -> System.Collections.Generic.IReadOnlyDictionary! @@ -10,6 +24,30 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Description.get Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Exception.get -> System.Exception? Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.HealthCheckResult(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description = null, System.Exception? exception = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Entries.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, System.TimeSpan totalDuration) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, System.TimeSpan totalDuration) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.TotalDuration.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry() -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Data.get -> System.Collections.Generic.IReadOnlyDictionary! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Description.get -> string? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Duration.get -> System.TimeSpan +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Exception.get -> System.Exception? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data, System.Collections.Generic.IEnumerable? tags = null) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Tags.get -> System.Collections.Generic.IEnumerable! +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Degraded = 1 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy = 2 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy = 0 -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck.CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext! context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher.PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport! report, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Degraded(string? description = null, System.Exception? exception = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Healthy(string? description = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Unhealthy(string? description = null, System.Exception? exception = null, System.Collections.Generic.IReadOnlyDictionary? data = null) -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..9cc16388d100 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1 +1,11 @@ #nullable enable +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Parameters.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Delay.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.HealthCheckRegistrationParameters(System.TimeSpan? delay = null, System.TimeSpan? period = null, System.TimeSpan? timeout = null, bool isEnabled = true) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.get -> bool +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.set -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Period.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Timeout.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void diff --git a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs index ac7b82a0f80d..ce28407dad08 100644 --- a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs +++ b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs @@ -13,13 +13,13 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// internal sealed class DelegateHealthCheck : IHealthCheck { - private readonly Func> _check; + private readonly Func> _check; /// /// Create an instance of from the specified delegate. /// /// A delegate which provides the code to execute when the health check is run. - public DelegateHealthCheck(Func> check) + public DelegateHealthCheck(Func> check) { _check = check ?? throw new ArgumentNullException(nameof(check)); } @@ -30,5 +30,5 @@ public DelegateHealthCheck(Func> chec /// A context object associated with the current execution. /// A that can be used to cancel the health check. /// A that completes when the health check has finished, yielding the status of the component being checked. - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken); + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken).AsTask(); } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index dc93f7a37fbe..baf284d59d9b 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -72,7 +72,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -82,7 +82,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckPublisherOptions? parameters = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -171,7 +171,7 @@ public static IHealthChecksBuilder AddCheck( /// /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -186,7 +186,7 @@ public static IHealthChecksBuilder AddCheck( HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckPublisherOptions? parameters = default) where T : class, IHealthCheck + HealthCheckRegistrationParameters? parameters = default) where T : class, IHealthCheck { if (builder == null) { @@ -325,7 +325,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. /// A representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -338,7 +338,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, - HealthCheckPublisherOptions? parameters, + HealthCheckRegistrationParameters? parameters, params object[] args) where T : class, IHealthCheck { if (builder == null) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index da2e9bf053ab..6bd33563acea 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -60,7 +60,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -69,7 +69,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckPublisherOptions? parameters = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -86,7 +86,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); + var instance = new DelegateHealthCheck((ct) => new ValueTask(check())); return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } @@ -135,7 +135,7 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( @@ -144,7 +144,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckPublisherOptions? parameters = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -161,7 +161,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); + var instance = new DelegateHealthCheck((ct) => new ValueTask(check(ct))); return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); } @@ -177,7 +177,7 @@ public static IHealthChecksBuilder AddCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags) { return AddAsyncCheck(builder, name, check, tags, default, default); @@ -195,7 +195,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags, TimeSpan? timeout) { @@ -210,16 +210,16 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags = default, TimeSpan? timeout = default, - HealthCheckPublisherOptions? parameters = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { @@ -252,7 +252,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags) { return AddAsyncCheck(builder, name, check, tags, default, default); @@ -270,7 +270,7 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags, TimeSpan? timeout) { @@ -285,16 +285,16 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. + /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags = null, TimeSpan? timeout = default, - HealthCheckPublisherOptions? parameters = default) + HealthCheckRegistrationParameters? parameters = default) { if (builder == null) { diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 979ba0b86dae..7cfe89265535 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -212,6 +212,12 @@ private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period, TimeSpan Timeo return false; } + // Check if HC is enabled + if (r.Parameters != null && !r.Parameters.IsEnabled) + { + return false; + } + if (_healthCheckPublisherOptions?.Value.Predicate == null) { return true; diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index 321cf8d588bd..2d8da1fe23c5 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -1,42 +1,4 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.HealthCheckContext() -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext.Registration.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.get -> System.Func! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Factory.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.FailureStatus.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.get -> string! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Name.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Tags.get -> System.Collections.Generic.ISet! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Entries.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, System.TimeSpan totalDuration) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.Collections.Generic.IReadOnlyDictionary! entries, System.TimeSpan totalDuration) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.TotalDuration.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry() -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Data.get -> System.Collections.Generic.IReadOnlyDictionary! -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Description.get -> string? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Duration.get -> System.TimeSpan -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Exception.get -> System.Exception? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string? description, System.TimeSpan duration, System.Exception? exception, System.Collections.Generic.IReadOnlyDictionary? data, System.Collections.Generic.IEnumerable? tags = null) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Tags.get -> System.Collections.Generic.IEnumerable! -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckPublisher.PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport! report, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck -Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck.CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext! context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! abstract Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.CheckHealthAsync(System.Func? predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions @@ -68,6 +30,10 @@ static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExten static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 64e24c022965..05ac9beac70f 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,15 +1,8 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Parameters.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckPublisherOptions? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 16b38db90b84..7693aa6f404f 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -41,9 +41,9 @@ public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); - b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); + b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); + b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); }); // Act @@ -144,9 +144,9 @@ public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); - b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); - b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); + b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); }); // Act @@ -250,8 +250,8 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu var service = CreateHealthChecksService(b => { b.AddAsyncCheck("Throws", ct => throw thrownException); - b.AddAsyncCheck("Faults", ct => Task.FromException(faultedException)); - b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy())); + b.AddAsyncCheck("Faults", ct => new ValueTask(Task.FromException(faultedException))); + b.AddAsyncCheck("Succeeds", ct => new ValueTask(HealthCheckResult.Healthy())); }); // Act @@ -301,7 +301,7 @@ public async Task CheckHealthAsync_SetsUpALoggerScopeForEachCheck() Assert.Equal("TestScope", item.Value); }); }); - return Task.FromResult(HealthCheckResult.Healthy()); + return new ValueTask(HealthCheckResult.Healthy()); }); var loggerFactory = new TestLoggerFactory(sink, enabled: true); diff --git a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs index 048ca2547dfd..b76c169bae3b 100644 --- a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs +++ b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs @@ -20,7 +20,7 @@ public void AddCheck_Instance() // Arrange var instance = new DelegateHealthCheck((_) => { - return Task.FromResult(HealthCheckResult.Healthy()); + return new ValueTask(HealthCheckResult.Healthy()); }); var services = CreateServices(); @@ -160,7 +160,7 @@ public void AddAsyncDelegateCheck_NoArg() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", () => { - return Task.FromResult(HealthCheckResult.Healthy()); + return new ValueTask(HealthCheckResult.Healthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -183,7 +183,7 @@ public void AddAsyncDelegateCheck_CancellationToken() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", (_) => { - return Task.FromResult(HealthCheckResult.Unhealthy()); + return new ValueTask(HealthCheckResult.Unhealthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -205,10 +205,10 @@ public void ChecksCanBeRegisteredInMultipleCallsToAddHealthChecks() var services = new ServiceCollection(); services .AddHealthChecks() - .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy())); + .AddAsyncCheck("Foo", () => new ValueTask(HealthCheckResult.Healthy())); services .AddHealthChecks() - .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy())); + .AddAsyncCheck("Bar", () => new ValueTask(HealthCheckResult.Healthy())); // Act var options = services.BuildServiceProvider().GetRequiredService>(); diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index dfe8fc7d0d83..95adda6e15c6 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -49,11 +49,11 @@ public void Constructor_ThrowsUsefulExceptionForDuplicateNames() serviceCollection.AddOptions(); serviceCollection.AddLogging(); serviceCollection.AddHealthChecks() - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); + .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))); // Choosing big values for tests to make sure that we're not dependent on the defaults. // All of the tests that rely on the timer will set their own values for speed. @@ -308,39 +308,34 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() { b.AddAsyncCheck("CheckDefault", _ => { - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }); b.AddAsyncCheck("CheckDelay2Period18", _ => { - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new() - { - Delay = TimeSpan.FromSeconds(2), - Period = TimeSpan.FromSeconds(18) - }); + parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); b.AddAsyncCheck("CheckDelay7Period11", _ => { - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new() - { - Delay = TimeSpan.FromSeconds(7), - Period = TimeSpan.FromSeconds(11) - }); + parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); b.AddAsyncCheck("CheckDelay9Period5", _ => { unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new() + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); + + b.AddAsyncCheck("DisabledCheck", _ => { - Delay = TimeSpan.FromSeconds(9), - Period = TimeSpan.FromSeconds(5) - }); + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); }); try @@ -549,39 +544,34 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() { b.AddAsyncCheck("CheckDefault", _ => { - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }); b.AddAsyncCheck("CheckDelay2Period18", _ => { - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new() - { - Delay = TimeSpan.FromSeconds(2), - Period = TimeSpan.FromSeconds(18) - }); + parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); b.AddAsyncCheck("CheckDelay7Period11", _ => { - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new() - { - Delay = TimeSpan.FromSeconds(7), - Period = TimeSpan.FromSeconds(11) - }); + parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); b.AddAsyncCheck("CheckDelay9Period5", _ => { unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); }, - parameters: new() + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); + + b.AddAsyncCheck("DisabledCheck", _ => { - Delay = TimeSpan.FromSeconds(9), - Period = TimeSpan.FromSeconds(5) - }); + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); + }, + parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); }); try From c819d741b3e9cb17a2525d53b48cd9cd58084ec1 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 15 Jan 2023 18:37:18 +0000 Subject: [PATCH 68/91] Refactor to HC Builder Add Registration --- .../src/HealthCheckRegistration.cs | 74 +++++- .../src/HealthCheckRegistrationParameters.cs | 51 ---- .../Abstractions/src/PublicAPI.Shipped.txt | 2 - .../Abstractions/src/PublicAPI.Unshipped.txt | 16 +- .../HealthChecks/src/DelegateHealthCheck.cs | 6 +- .../HealthChecksBuilderAddCheckExtensions.cs | 159 ++++-------- .../HealthChecksBuilderDelegateExtensions.cs | 132 ++-------- .../HealthChecks/src/HealthCheckContext.cs | 16 -- .../src/HealthCheckRegistration.cs | 245 ------------------ .../HealthChecks/src/HealthReport.cs | 81 ------ .../HealthChecks/src/HealthReportEntry.cs | 80 ------ .../HealthChecks/src/IHealthCheck.cs | 22 -- .../HealthChecks/src/IHealthCheckPublisher.cs | 38 --- .../HealthChecks/src/PublicAPI.Shipped.txt | 24 +- .../HealthChecks/src/PublicAPI.Unshipped.txt | 7 - 15 files changed, 160 insertions(+), 793 deletions(-) delete mode 100644 src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs delete mode 100644 src/HealthChecks/HealthChecks/src/HealthCheckContext.cs delete mode 100644 src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs delete mode 100644 src/HealthChecks/HealthChecks/src/HealthReport.cs delete mode 100644 src/HealthChecks/HealthChecks/src/HealthReportEntry.cs delete mode 100644 src/HealthChecks/HealthChecks/src/IHealthCheck.cs delete mode 100644 src/HealthChecks/HealthChecks/src/IHealthCheckPublisher.cs diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index 0f514ca27691..f7cb4030cc2b 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -53,7 +53,24 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, instance, failureStatus, tags, timeout, default) + : this(name, instance, failureStatus, tags, timeout, default, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// The instance. + /// + /// The that should be reported upon failure of the health check. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + /// An optional representing the delay of the check. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? delay) + : this(name, instance, failureStatus, tags, timeout, delay, default) { } @@ -68,8 +85,9 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check parameters. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckRegistrationParameters? parameters) + /// An optional representing the delay of the check. + /// An optional representing the period of the check. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? delay, TimeSpan? period) { if (name == null) { @@ -91,7 +109,8 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = (_) => instance; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Parameters = parameters; + Delay = delay; + Period = period; } /// @@ -109,7 +128,7 @@ public HealthCheckRegistration( Func factory, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, factory, failureStatus, tags, default, default) + : this(name, factory, failureStatus, tags, default, default, default) { } @@ -130,7 +149,7 @@ public HealthCheckRegistration( HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, factory, failureStatus, tags, timeout, default) + : this(name, factory, failureStatus, tags, timeout, default, default) { } @@ -145,14 +164,39 @@ public HealthCheckRegistration( /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the individual health check parameters. + /// An optional representing the delay of the check. public HealthCheckRegistration( string name, Func factory, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, - HealthCheckRegistrationParameters? parameters) + TimeSpan? delay) + : this(name, factory, failureStatus, tags, timeout, delay, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// A delegate used to create the instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + /// An optional representing the delay of the check. + /// An optional representing the period of the check. + public HealthCheckRegistration( + string name, + Func factory, + HealthStatus? failureStatus, + IEnumerable? tags, + TimeSpan? timeout, + TimeSpan? delay, + TimeSpan? period) { if (name == null) { @@ -174,7 +218,8 @@ public HealthCheckRegistration( Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = factory; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Parameters = parameters; + Delay = delay; + Period = period; } /// @@ -217,9 +262,16 @@ public TimeSpan Timeout } /// - /// Gets the individual parameters associated with a health check. + /// Gets the individual delay applied to the health check after the application starts before executing + /// instances. The delay is applied once at startup, and does + /// not apply to subsequent iterations. + /// + public TimeSpan? Delay { get; } + + /// + /// Gets the individual period used for the check. /// - public HealthCheckRegistrationParameters? Parameters { get; } + public TimeSpan? Period { get; } /// /// Gets or sets the health check name. diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs deleted file mode 100644 index 5f076598fb62..000000000000 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistrationParameters.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represent the individual health check parameters associated with an . -/// -public class HealthCheckRegistrationParameters -{ - /// - /// Creates a new . - /// - /// An optional representing the individual delay applied after the application starts before executing the check. - /// An optional representing the individual period of the check. - /// An optional representing the individual timeout of the check. - /// An optional representing whether the health check should be run. - public HealthCheckRegistrationParameters(TimeSpan? delay = default, TimeSpan? period = default, TimeSpan? timeout = default, bool isEnabled = true) - { - Delay = delay; - Period = period; - Timeout = timeout; - IsEnabled = isEnabled; - } - - /// - /// Gets or sets the individual delay applied to the health check after the application starts before executing - /// instances. The delay is applied once at startup, and does - /// not apply to subsequent iterations. - /// - public TimeSpan? Delay { get; } - - /// - /// Gets the individual period used for the check. - /// - public TimeSpan? Period { get; } - - /// - /// Gets the individual timeout for executing the health check. - /// Use to execute with no timeout. - /// - public TimeSpan? Timeout { get; } - - /// - /// Gets or sets whether the health check should be run. Enabled by default. - /// - public bool IsEnabled { get; set; } -} diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt index 6d8294f838d6..03e65dd8f781 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Shipped.txt @@ -18,7 +18,6 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Tags.get - Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.get -> System.TimeSpan Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Timeout.set -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.HealthCheckResult() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Data.get -> System.Collections.Generic.IReadOnlyDictionary! Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Description.get -> string? Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult.Exception.get -> System.Exception? @@ -31,7 +30,6 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.HealthReport(System.C Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Status.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.TotalDuration.get -> System.TimeSpan Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry -Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.HealthReportEntry() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Data.get -> System.Collections.Generic.IReadOnlyDictionary! Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Description.get -> string? Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry.Duration.get -> System.TimeSpan diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index 9cc16388d100..f0853697c6d8 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1,11 +1,7 @@ #nullable enable -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Parameters.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Delay.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.HealthCheckRegistrationParameters(System.TimeSpan? delay = null, System.TimeSpan? period = null, System.TimeSpan? timeout = null, bool isEnabled = true) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.get -> bool -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.IsEnabled.set -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Period.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters.Timeout.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.get -> System.TimeSpan? diff --git a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs index ce28407dad08..ac7b82a0f80d 100644 --- a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs +++ b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs @@ -13,13 +13,13 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; /// internal sealed class DelegateHealthCheck : IHealthCheck { - private readonly Func> _check; + private readonly Func> _check; /// /// Create an instance of from the specified delegate. /// /// A delegate which provides the code to execute when the health check is run. - public DelegateHealthCheck(Func> check) + public DelegateHealthCheck(Func> check) { _check = check ?? throw new ArgumentNullException(nameof(check)); } @@ -30,5 +30,5 @@ public DelegateHealthCheck(Func> /// A context object associated with the current execution. /// A that can be used to cancel the health check. /// A that completes when the health check has finished, yielding the status of the component being checked. - public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken).AsTask(); + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken); } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index baf284d59d9b..44f2436f1d00 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -31,9 +31,9 @@ public static IHealthChecksBuilder AddCheck( string name, IHealthCheck instance, HealthStatus? failureStatus, - IEnumerable? tags) + IEnumerable tags) { - return AddCheck(builder, name, instance, failureStatus, tags, default, default); + return AddCheck(builder, name, instance, failureStatus, tags, default); } /// @@ -49,40 +49,14 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// An optional representing the timeout of the check. /// The . - public static IHealthChecksBuilder AddCheck( - this IHealthChecksBuilder builder, - string name, - IHealthCheck instance, - HealthStatus? failureStatus, - IEnumerable? tags, - TimeSpan? timeout) - { - return AddCheck(builder, name, instance, failureStatus, tags, timeout, default); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// An instance. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used to filter health checks. - /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. - /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, IHealthCheck instance, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + HealthStatus? failureStatus = null, + IEnumerable? tags = null, + TimeSpan? timeout = null) { if (builder == null) { @@ -99,7 +73,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout, parameters)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout)); } /// @@ -125,9 +99,9 @@ public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, - IEnumerable? tags) where T : class, IHealthCheck + IEnumerable tags) where T : class, IHealthCheck { - return AddCheck(builder, name, failureStatus, tags, default, default); + return AddCheck(builder, name, failureStatus, tags, default); } /// @@ -152,41 +126,9 @@ public static IHealthChecksBuilder AddCheck( public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, - HealthStatus? failureStatus, - IEnumerable? tags, - TimeSpan? timeout) where T : class, IHealthCheck - { - return AddCheck(builder, name, failureStatus, tags, timeout, default); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The health check implementation type. - /// The . - /// The name of the health check. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used to filter health checks. - /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. - /// The . - /// - /// This method will use to create the health check - /// instance when needed. If a service of type is registered in the dependency injection container - /// with any lifetime it will be used. Otherwise an instance of type will be constructed with - /// access to services from the dependency injection container. - /// - [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")] - public static IHealthChecksBuilder AddCheck<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( - this IHealthChecksBuilder builder, - string name, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) where T : class, IHealthCheck + HealthStatus? failureStatus = null, + IEnumerable? tags = null, + TimeSpan? timeout = null) where T : class, IHealthCheck { if (builder == null) { @@ -198,7 +140,7 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout, parameters)); + return builder.Add(new HealthCheckRegistration(name, GetServiceOrCreateInstance, failureStatus, tags, timeout)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] @@ -225,33 +167,17 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( this IHealthChecksBuilder builder, string name, params object[] args) where T : class, IHealthCheck { - return AddTypeActivatedCheck(builder, name, failureStatus: default, tags: default, args); - } + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } - /// - /// Adds a new type activated health check with the specified name and implementation. - /// - /// The health check implementation type. - /// The . - /// The name of the health check. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// Additional arguments to provide to the constructor. - /// The . - /// - /// This method will use to create the health check - /// instance when needed. Additional arguments can be provided to the constructor via . - /// - public static IHealthChecksBuilder AddTypeActivatedCheck< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>( - this IHealthChecksBuilder builder, - string name, - HealthStatus? failureStatus, - params object[] args) where T : class, IHealthCheck - { - return AddTypeActivatedCheck(builder, name, failureStatus, tags: default, timeout: default, parameters: default, args); + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return AddTypeActivatedCheck(builder, name, failureStatus: null, tags: null, args); } /// @@ -264,7 +190,6 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// The that should be reported when the health check reports a failure. If the provided value /// is null, then will be reported. /// - /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. /// The . /// @@ -276,10 +201,19 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, - IEnumerable? tags, params object[] args) where T : class, IHealthCheck { - return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: default, parameters: default, args); + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return AddTypeActivatedCheck(builder, name, failureStatus, tags: null, args); } /// @@ -294,7 +228,6 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. - /// A representing the timeout of the check. /// The . /// /// This method will use to create the health check @@ -306,10 +239,24 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< string name, HealthStatus? failureStatus, IEnumerable? tags, - TimeSpan? timeout, params object[] args) where T : class, IHealthCheck { - return AddTypeActivatedCheck(builder, name, failureStatus, tags: tags, timeout: timeout, parameters: default, args); + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags)); + + [UnconditionalSuppressMessage("Trimming", "IL2091", + Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] + T CreateInstance(IServiceProvider serviceProvider) => + ActivatorUtilities.CreateInstance(serviceProvider, args); } /// @@ -325,7 +272,6 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< /// A list of tags that can be used to filter health checks. /// Additional arguments to provide to the constructor. /// A representing the timeout of the check. - /// An optional representing the individual health check options. /// The . /// /// This method will use to create the health check @@ -336,9 +282,8 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus, - IEnumerable? tags, - TimeSpan? timeout, - HealthCheckRegistrationParameters? parameters, + IEnumerable tags, + TimeSpan timeout, params object[] args) where T : class, IHealthCheck { if (builder == null) @@ -351,7 +296,7 @@ public static IHealthChecksBuilder AddTypeActivatedCheck< throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout, parameters)); + return builder.Add(new HealthCheckRegistration(name, CreateInstance, failureStatus, tags, timeout)); [UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "DynamicallyAccessedMemberTypes.PublicConstructors is enforced by calling method.")] diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index 6bd33563acea..441e70152dac 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -28,28 +28,9 @@ public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable? tags) - { - return AddCheck(builder, name, check, tags, default, default); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// The . - public static IHealthChecksBuilder AddCheck( - this IHealthChecksBuilder builder, - string name, - Func check, - IEnumerable? tags, - TimeSpan? timeout) + IEnumerable tags) { - return AddCheck(builder, name, check, tags, timeout, default); + return AddCheck(builder, name, check, tags, default); } /// @@ -60,16 +41,14 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable? tags = default, - TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + IEnumerable? tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -86,8 +65,8 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => new ValueTask(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -105,26 +84,7 @@ public static IHealthChecksBuilder AddCheck( Func check, IEnumerable? tags) { - return AddCheck(builder, name, check, tags, default, default); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// The . - public static IHealthChecksBuilder AddCheck( - this IHealthChecksBuilder builder, - string name, - Func check, - IEnumerable? tags, - TimeSpan? timeout) - { - return AddCheck(builder, name, check, tags, timeout, default); + return AddCheck(builder, name, check, tags, default); } /// @@ -135,16 +95,14 @@ public static IHealthChecksBuilder AddCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable? tags = default, - TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + IEnumerable? tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -161,8 +119,8 @@ public static IHealthChecksBuilder AddCheck( throw new ArgumentNullException(nameof(check)); } - var instance = new DelegateHealthCheck((ct) => new ValueTask(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -177,29 +135,10 @@ public static IHealthChecksBuilder AddCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, - IEnumerable? tags) - { - return AddAsyncCheck(builder, name, check, tags, default, default); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// The . - public static IHealthChecksBuilder AddAsyncCheck( - this IHealthChecksBuilder builder, - string name, - Func> check, - IEnumerable? tags, - TimeSpan? timeout) + Func> check, + IEnumerable tags) { - return AddAsyncCheck(builder, name, check, tags, timeout, default); + return AddAsyncCheck(builder, name, check, tags, default); } /// @@ -210,16 +149,14 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, - IEnumerable? tags = default, - TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + Func> check, + IEnumerable? tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -237,7 +174,7 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -252,29 +189,10 @@ public static IHealthChecksBuilder AddAsyncCheck( public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, - IEnumerable? tags) - { - return AddAsyncCheck(builder, name, check, tags, default, default); - } - - /// - /// Adds a new health check with the specified name and implementation. - /// - /// The . - /// The name of the health check. - /// A list of tags that can be used to filter health checks. - /// A delegate that provides the health check implementation. - /// An optional representing the timeout of the check. - /// The . - public static IHealthChecksBuilder AddAsyncCheck( - this IHealthChecksBuilder builder, - string name, - Func> check, - IEnumerable? tags, - TimeSpan? timeout) + Func> check, + IEnumerable tags) { - return AddAsyncCheck(builder, name, check, tags, timeout, default); + return AddAsyncCheck(builder, name, check, tags, default); } /// @@ -285,16 +203,14 @@ public static IHealthChecksBuilder AddAsyncCheck( /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// An optional representing the timeout of the check. - /// An optional representing the individual health check options. /// The . [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")] public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, - Func> check, + Func> check, IEnumerable? tags = null, - TimeSpan? timeout = default, - HealthCheckRegistrationParameters? parameters = default) + TimeSpan? timeout = default) { if (builder == null) { @@ -312,6 +228,6 @@ public static IHealthChecksBuilder AddAsyncCheck( } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: default, tags, timeout, parameters)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } } diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckContext.cs b/src/HealthChecks/HealthChecks/src/HealthCheckContext.cs deleted file mode 100644 index cef55408ef52..000000000000 --- a/src/HealthChecks/HealthChecks/src/HealthCheckContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Health check context. Provides health check registrations to . -/// -public sealed class HealthCheckContext -{ - /// - /// Gets or sets the of the currently executing . - /// - // This allows null values for convenience during unit testing. This is expected to be non-null when within application code. - public HealthCheckRegistration Registration { get; set; } = default!; -} diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs b/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs deleted file mode 100644 index 07a57f6135f6..000000000000 --- a/src/HealthChecks/HealthChecks/src/HealthCheckRegistration.cs +++ /dev/null @@ -1,245 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represent the registration information associated with an implementation. -/// -/// -/// -/// The health check registration is provided as a separate object so that application developers can customize -/// how health check implementations are configured. -/// -/// -/// The registration is provided to an implementation during execution through -/// . This allows a health check implementation to access named -/// options or perform other operations based on the registered name. -/// -/// -public sealed class HealthCheckRegistration -{ - private Func _factory; - private string _name; - private TimeSpan _timeout; - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// The instance. - /// - /// The that should be reported upon failure of the health check. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, instance, failureStatus, tags, default, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// The instance. - /// - /// The that should be reported upon failure of the health check. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, instance, failureStatus, tags, timeout, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// The instance. - /// - /// The that should be reported upon failure of the health check. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - /// An optional representing the individual health check parameters. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, HealthCheckPublisherOptions? parameters) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(timeout)); - } - - _name = name; - FailureStatus = failureStatus ?? HealthStatus.Unhealthy; - Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); - _factory = (_) => instance; - Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Parameters = parameters; - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// A delegate used to create the instance. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - public HealthCheckRegistration( - string name, - Func factory, - HealthStatus? failureStatus, - IEnumerable? tags) - : this(name, factory, failureStatus, tags, default, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// A delegate used to create the instance. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - public HealthCheckRegistration( - string name, - Func factory, - HealthStatus? failureStatus, - IEnumerable? tags, - TimeSpan? timeout) - : this(name, factory, failureStatus, tags, timeout, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// A delegate used to create the instance. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - /// An optional representing the individual health check parameters. - public HealthCheckRegistration( - string name, - Func factory, - HealthStatus? failureStatus, - IEnumerable? tags, - TimeSpan? timeout, - HealthCheckPublisherOptions? parameters) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(timeout)); - } - - _name = name; - FailureStatus = failureStatus ?? HealthStatus.Unhealthy; - Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); - _factory = factory; - Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Parameters = parameters; - } - - /// - /// Gets or sets a delegate used to create the instance. - /// - public Func Factory - { - get => _factory; - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _factory = value; - } - } - - /// - /// Gets or sets the that should be reported upon failure of the health check. - /// - public HealthStatus FailureStatus { get; set; } - - /// - /// Gets or sets the timeout used for the test. - /// - public TimeSpan Timeout - { - get => _timeout; - set - { - if (value <= TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _timeout = value; - } - } - - /// - /// Gets the individual parameters associated with a health check. - /// - public HealthCheckPublisherOptions? Parameters { get; } - - /// - /// Gets or sets the health check name. - /// - public string Name - { - get => _name; - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _name = value; - } - } - - /// - /// Gets a list of tags that can be used for filtering health checks. - /// - public ISet Tags { get; } -} diff --git a/src/HealthChecks/HealthChecks/src/HealthReport.cs b/src/HealthChecks/HealthChecks/src/HealthReport.cs deleted file mode 100644 index 829067fb3cfa..000000000000 --- a/src/HealthChecks/HealthChecks/src/HealthReport.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represents the result of executing a group of instances. -/// -public sealed class HealthReport -{ - /// - /// Create a new from the specified results. - /// - /// A containing the results from each health check. - /// A value indicating the time the health check service took to execute. - public HealthReport(IReadOnlyDictionary entries, TimeSpan totalDuration) - : this( - entries, - CalculateAggregateStatus(entries?.Values ?? throw new ArgumentNullException(nameof(entries))), - totalDuration) - { - } - - /// - /// Create a new from the specified results. - /// - /// A containing the results from each health check. - /// A representing the aggregate status of all the health checks. - /// A value indicating the time the health check service took to execute. - public HealthReport(IReadOnlyDictionary entries, HealthStatus status, TimeSpan totalDuration) - { - Entries = entries; - Status = status; - TotalDuration = totalDuration; - } - - /// - /// A containing the results from each health check. - /// - /// - /// The keys in this dictionary map the name of each executed health check to a for the - /// result data returned from the corresponding health check. - /// - public IReadOnlyDictionary Entries { get; } - - /// - /// Gets a representing the aggregate status of all the health checks. The value of - /// will be the most severe status reported by a health check. If no checks were executed, the value is always . - /// - public HealthStatus Status { get; } - - /// - /// Gets the time the health check service took to execute. - /// - public TimeSpan TotalDuration { get; } - - private static HealthStatus CalculateAggregateStatus(IEnumerable entries) - { - // This is basically a Min() check, but we know the possible range, so we don't need to walk the whole list - var currentValue = HealthStatus.Healthy; - foreach (var entry in entries) - { - if (currentValue > entry.Status) - { - currentValue = entry.Status; - } - - if (currentValue == HealthStatus.Unhealthy) - { - // Game over, man! Game over! - // (We hit the worst possible status, so there's no need to keep iterating) - return currentValue; - } - } - - return currentValue; - } -} diff --git a/src/HealthChecks/HealthChecks/src/HealthReportEntry.cs b/src/HealthChecks/HealthChecks/src/HealthReportEntry.cs deleted file mode 100644 index 3f79df0a7033..000000000000 --- a/src/HealthChecks/HealthChecks/src/HealthReportEntry.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represents an entry in a . Corresponds to the result of a single . -/// -public struct HealthReportEntry -{ - private static readonly IReadOnlyDictionary _emptyReadOnlyDictionary = new Dictionary(); - - /// - /// Creates a new with the specified values for , , - /// , and . - /// - /// A value indicating the health status of the component that was checked. - /// A human-readable description of the status of the component that was checked. - /// A value indicating the health execution duration. - /// An representing the exception that was thrown when checking for status (if any). - /// Additional key-value pairs describing the health of the component. - public HealthReportEntry(HealthStatus status, string? description, TimeSpan duration, Exception? exception, IReadOnlyDictionary? data) - : this(status, description, duration, exception, data, null) - { - } - - /// - /// Creates a new with the specified values for , , - /// , and . - /// - /// A value indicating the health status of the component that was checked. - /// A human-readable description of the status of the component that was checked. - /// A value indicating the health execution duration. - /// An representing the exception that was thrown when checking for status (if any). - /// Additional key-value pairs describing the health of the component. - /// Tags associated with the health check that generated the report entry. - public HealthReportEntry(HealthStatus status, string? description, TimeSpan duration, Exception? exception, IReadOnlyDictionary? data, IEnumerable? tags = null) - { - Status = status; - Description = description; - Duration = duration; - Exception = exception; - Data = data ?? _emptyReadOnlyDictionary; - Tags = tags ?? Enumerable.Empty(); - } - - /// - /// Gets additional key-value pairs describing the health of the component. - /// - public IReadOnlyDictionary Data { get; } - - /// - /// Gets a human-readable description of the status of the component that was checked. - /// - public string? Description { get; } - - /// - /// Gets the health check execution duration. - /// - public TimeSpan Duration { get; } - - /// - /// Gets an representing the exception that was thrown when checking for status (if any). - /// - public Exception? Exception { get; } - - /// - /// Gets the health status of the component that was checked. - /// - public HealthStatus Status { get; } - - /// - /// Gets the tags associated with the health check. - /// - public IEnumerable Tags { get; } -} diff --git a/src/HealthChecks/HealthChecks/src/IHealthCheck.cs b/src/HealthChecks/HealthChecks/src/IHealthCheck.cs deleted file mode 100644 index d806a7ecbe40..000000000000 --- a/src/HealthChecks/HealthChecks/src/IHealthCheck.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represents a health check, which can be used to check the status of a component in the application, such as a backend service, database or some internal -/// state. -/// -public interface IHealthCheck -{ - /// - /// Runs the health check, returning the status of the component being checked. - /// - /// A context object associated with the current execution. - /// A that can be used to cancel the health check. - /// A that completes when the health check has finished, yielding the status of the component being checked. - Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default); -} diff --git a/src/HealthChecks/HealthChecks/src/IHealthCheckPublisher.cs b/src/HealthChecks/HealthChecks/src/IHealthCheckPublisher.cs deleted file mode 100644 index 44661b347a73..000000000000 --- a/src/HealthChecks/HealthChecks/src/IHealthCheckPublisher.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.Diagnostics.HealthChecks; - -/// -/// Represents a publisher of information. -/// -/// -/// -/// The default health checks implementation provided an IHostedService implementation that can -/// be used to execute health checks at regular intervals and provide the resulting -/// data to all registered instances. -/// -/// -/// To provide an implementation, register an instance or type as a singleton -/// service in the dependency injection container. -/// -/// -/// instances are provided with a after executing -/// health checks in a background thread. The use of depend on hosting in -/// an application using IWebHost or generic host (IHost). Execution of -/// instance is not related to execution of health checks via a middleware. -/// -/// -public interface IHealthCheckPublisher -{ - /// - /// Publishes the provided . - /// - /// The . The result of executing a set of health checks. - /// The . - /// A which will complete when publishing is complete. - Task PublishAsync(HealthReport report, CancellationToken cancellationToken); -} diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt index 2d8da1fe23c5..a6dae5afb4a7 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Shipped.txt @@ -22,20 +22,20 @@ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService.HealthCheckServ Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.HealthCheckServiceOptions() -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckServiceOptions.Registrations.get -> System.Collections.Generic.ICollection! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable! tags, System.TimeSpan timeout, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func!>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable! tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! +static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! static Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions.AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! diff --git a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt index 05ac9beac70f..7dc5c58110bf 100644 --- a/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/HealthChecks/src/PublicAPI.Unshipped.txt @@ -1,8 +1 @@ #nullable enable -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = null, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters, params object![]! args) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func>! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! -static Microsoft.Extensions.DependencyInjection.HealthChecksBuilderDelegateExtensions.AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! builder, string! name, System.Func! check, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistrationParameters? parameters = null) -> Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder! From 54cd6b96b41ec58700bc2ef915eed05b395d96be Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 15 Jan 2023 18:37:36 +0000 Subject: [PATCH 69/91] Align tests --- .../test/DefaultHealthCheckServiceTest.cs | 18 +- .../HealthChecksBuilderTest.cs | 10 +- .../HealthCheckPublisherHostedServiceTest.cs | 166 +++++++++++------- 3 files changed, 115 insertions(+), 79 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 7693aa6f404f..16b38db90b84 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -41,9 +41,9 @@ public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); - b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); - b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); }); // Act @@ -144,9 +144,9 @@ public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync() var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => new ValueTask(HealthCheckResult.Healthy(HealthyMessage, data))); - b.AddAsyncCheck("DegradedCheck", _ => new ValueTask(HealthCheckResult.Degraded(DegradedMessage))); - b.AddAsyncCheck("UnhealthyCheck", _ => new ValueTask(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); }); // Act @@ -250,8 +250,8 @@ public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResu var service = CreateHealthChecksService(b => { b.AddAsyncCheck("Throws", ct => throw thrownException); - b.AddAsyncCheck("Faults", ct => new ValueTask(Task.FromException(faultedException))); - b.AddAsyncCheck("Succeeds", ct => new ValueTask(HealthCheckResult.Healthy())); + b.AddAsyncCheck("Faults", ct => Task.FromException(faultedException)); + b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy())); }); // Act @@ -301,7 +301,7 @@ public async Task CheckHealthAsync_SetsUpALoggerScopeForEachCheck() Assert.Equal("TestScope", item.Value); }); }); - return new ValueTask(HealthCheckResult.Healthy()); + return Task.FromResult(HealthCheckResult.Healthy()); }); var loggerFactory = new TestLoggerFactory(sink, enabled: true); diff --git a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs index b76c169bae3b..048ca2547dfd 100644 --- a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs +++ b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs @@ -20,7 +20,7 @@ public void AddCheck_Instance() // Arrange var instance = new DelegateHealthCheck((_) => { - return new ValueTask(HealthCheckResult.Healthy()); + return Task.FromResult(HealthCheckResult.Healthy()); }); var services = CreateServices(); @@ -160,7 +160,7 @@ public void AddAsyncDelegateCheck_NoArg() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", () => { - return new ValueTask(HealthCheckResult.Healthy()); + return Task.FromResult(HealthCheckResult.Healthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -183,7 +183,7 @@ public void AddAsyncDelegateCheck_CancellationToken() var services = CreateServices(); services.AddHealthChecks().AddAsyncCheck("test", (_) => { - return new ValueTask(HealthCheckResult.Unhealthy()); + return Task.FromResult(HealthCheckResult.Unhealthy()); }, tags: new[] { "tag", }); var serviceProvider = services.BuildServiceProvider(); @@ -205,10 +205,10 @@ public void ChecksCanBeRegisteredInMultipleCallsToAddHealthChecks() var services = new ServiceCollection(); services .AddHealthChecks() - .AddAsyncCheck("Foo", () => new ValueTask(HealthCheckResult.Healthy())); + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy())); services .AddHealthChecks() - .AddAsyncCheck("Bar", () => new ValueTask(HealthCheckResult.Healthy())); + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy())); // Act var options = services.BuildServiceProvider().GetRequiredService>(); diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 95adda6e15c6..55369fec74f9 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -49,11 +49,11 @@ public void Constructor_ThrowsUsefulExceptionForDuplicateNames() serviceCollection.AddOptions(); serviceCollection.AddLogging(); serviceCollection.AddHealthChecks() - .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Foo", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Bar", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => new ValueTask(HealthCheckResult.Healthy()))); + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); // Choosing big values for tests to make sure that we're not dependent on the defaults. // All of the tests that rely on the timer will set their own values for speed. @@ -306,36 +306,54 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() var service = CreateService(publishers, configureBuilder: b => { - b.AddAsyncCheck("CheckDefault", _ => - { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }); - - b.AddAsyncCheck("CheckDelay2Period18", _ => - { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); - - b.AddAsyncCheck("CheckDelay7Period11", _ => - { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); - - b.AddAsyncCheck("CheckDelay9Period5", _ => - { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); - - b.AddAsyncCheck("DisabledCheck", _ => - { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); + b.Add( + new HealthCheckRegistration( + name: "CheckDefault", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null)); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay2Period18", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default, + delay: TimeSpan.FromSeconds(2), + period: TimeSpan.FromSeconds(18))); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay7Period11", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default, + delay: TimeSpan.FromSeconds(7), + period: TimeSpan.FromSeconds(11))); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay9Period5", + instance: new DelegateHealthCheck(_ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + }), + failureStatus: null, + tags: null, + timeout: default, + delay: TimeSpan.FromSeconds(9), + period: TimeSpan.FromSeconds(5))); + + // TODO Disabled + //b.AddAsyncCheck("DisabledCheck", _ => + //{ + // unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + // return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + //}, + //parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); }); try @@ -542,36 +560,54 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() }, configureBuilder: b => { - b.AddAsyncCheck("CheckDefault", _ => - { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }); - - b.AddAsyncCheck("CheckDelay2Period18", _ => - { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(2), period: TimeSpan.FromSeconds(18))); - - b.AddAsyncCheck("CheckDelay7Period11", _ => - { - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(7), period: TimeSpan.FromSeconds(11))); - - b.AddAsyncCheck("CheckDelay9Period5", _ => - { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); - - b.AddAsyncCheck("DisabledCheck", _ => - { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return new ValueTask(HealthCheckResult.Healthy(HealthyMessage)); - }, - parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); + b.Add( + new HealthCheckRegistration( + name: "CheckDefault", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null)); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay2Period18", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default, + delay: TimeSpan.FromSeconds(2), + period: TimeSpan.FromSeconds(18))); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay7Period11", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default, + delay: TimeSpan.FromSeconds(7), + period: TimeSpan.FromSeconds(11))); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay9Period5", + instance: new DelegateHealthCheck(_ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + }), + failureStatus: null, + tags: null, + timeout: default, + delay: TimeSpan.FromSeconds(9), + period: TimeSpan.FromSeconds(5))); + + // TODO Disabled + //b.AddAsyncCheck("DisabledCheck", _ => + //{ + // unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check + // return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + //}, + //parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); }); try From 18cb9d6d9858783bb1ecb53575bc8f60657bfc12 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 15 Jan 2023 18:38:00 +0000 Subject: [PATCH 70/91] Remove aggregate by timeout --- .../src/HealthCheckPublisherHostedService.cs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 7cfe89265535..5385002cca1c 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -22,9 +22,9 @@ internal sealed partial class HealthCheckPublisherHostedService : IHostedService private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; - private readonly (TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) _defaultTimerOptions; - private readonly Dictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), List> _healthChecksByOptions; - private Dictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer>? _timersByOptions; + private readonly (TimeSpan Delay, TimeSpan Period) _defaultTimerOptions; + private readonly Dictionary<(TimeSpan Delay, TimeSpan Period), List> _healthChecksByOptions; + private Dictionary<(TimeSpan Delay, TimeSpan Period), Timer>? _timersByOptions; private readonly CancellationTokenSource _stopping; private CancellationTokenSource? _runTokenSource; @@ -74,16 +74,16 @@ public HealthCheckPublisherHostedService( // actually tries to **run** health checks would be real baaaaad. ValidateRegistrations(_healthCheckServiceOptions.Value.Registrations); - _defaultTimerOptions = (_healthCheckPublisherOptions.Value.Delay, _healthCheckPublisherOptions.Value.Period, _healthCheckPublisherOptions.Value.Timeout); + _defaultTimerOptions = (_healthCheckPublisherOptions.Value.Delay, _healthCheckPublisherOptions.Value.Period); // Group healthcheck registrations by Delay, Period and Timeout, to build a Dictionary<(TimeSpan, TimeSpan, TimeSpan), List> // For HCs with no Delay, Period or Timeout, we default to the publisher values _healthChecksByOptions = _healthCheckServiceOptions.Value.Registrations.GroupBy(GetTimerOptionsOrDefault).ToDictionary(g => g.Key, g => g.ToList()); } - private (TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) GetTimerOptionsOrDefault(HealthCheckRegistration registration) + private (TimeSpan Delay, TimeSpan Period) GetTimerOptionsOrDefault(HealthCheckRegistration registration) { - return (registration.Parameters?.Delay ?? _healthCheckPublisherOptions.Value.Delay, registration.Parameters?.Period ?? _healthCheckPublisherOptions.Value.Period, registration.Parameters?.Timeout ?? _healthCheckPublisherOptions.Value.Timeout); + return (registration?.Delay ?? _healthCheckPublisherOptions.Value.Delay, registration?.Period ?? _healthCheckPublisherOptions.Value.Period); } internal bool IsStopping => _stopping.IsCancellationRequested; @@ -133,14 +133,14 @@ public Task StopAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - private Dictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer> CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), List> healthChecksByOptions) + private Dictionary<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period), List> healthChecksByOptions) { return healthChecksByOptions.Select(m => CreateTimer(m.Key)).ToDictionary(kv => kv.Key, kv => kv.Value); } - private KeyValuePair<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer> CreateTimer((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) timerOptions) + private KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimer((TimeSpan Delay, TimeSpan Period) timerOptions) { - return new KeyValuePair<(TimeSpan Delay, TimeSpan Period, TimeSpan Timeout), Timer>( + return new KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer>( timerOptions, NonCapturingTimer.Create( async (state) => @@ -160,7 +160,7 @@ internal void CancelToken() } // Internal for testing - internal async Task RunAsync((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout)? timerOptions = null) + internal async Task RunAsync((TimeSpan Delay, TimeSpan Period)? timerOptions = null) { timerOptions ??= _defaultTimerOptions; @@ -170,7 +170,7 @@ internal async Task RunAsync((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) CancellationTokenSource? cancellation = null; try { - var timeout = timerOptions.Value.Timeout; + var timeout = _healthCheckPublisherOptions.Value.Timeout; cancellation = CancellationTokenSource.CreateLinkedTokenSource(_stopping.Token); _runTokenSource = cancellation; @@ -196,7 +196,7 @@ internal async Task RunAsync((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) } } - private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period, TimeSpan Timeout) timerOptions, CancellationToken cancellationToken) + private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period) timerOptions, CancellationToken cancellationToken) { // Forcibly yield - we want to unblock the timer thread. await Task.Yield(); @@ -212,11 +212,7 @@ private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period, TimeSpan Timeo return false; } - // Check if HC is enabled - if (r.Parameters != null && !r.Parameters.IsEnabled) - { - return false; - } + // TODO Check if HC is enabled if (_healthCheckPublisherOptions?.Value.Predicate == null) { From 25899b0026d72b9dc715bd98a273b83dd5a11c76 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 15 Jan 2023 18:43:43 +0000 Subject: [PATCH 71/91] Remove IsEnabled references --- .../Abstractions/src/IHealthCheck.cs | 1 + .../src/HealthCheckPublisherHostedService.cs | 2 -- .../HealthCheckPublisherHostedServiceTest.cs | 16 ---------------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/IHealthCheck.cs b/src/HealthChecks/Abstractions/src/IHealthCheck.cs index d806a7ecbe40..b7b1ab1a9b53 100644 --- a/src/HealthChecks/Abstractions/src/IHealthCheck.cs +++ b/src/HealthChecks/Abstractions/src/IHealthCheck.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 5385002cca1c..4e1e11a61e39 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -212,8 +212,6 @@ private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period) timerOptions, return false; } - // TODO Check if HC is enabled - if (_healthCheckPublisherOptions?.Value.Predicate == null) { return true; diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 55369fec74f9..97cbeea91a6c 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -346,14 +346,6 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() timeout: default, delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); - - // TODO Disabled - //b.AddAsyncCheck("DisabledCheck", _ => - //{ - // unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - // return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); - //}, - //parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); }); try @@ -600,14 +592,6 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() timeout: default, delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5))); - - // TODO Disabled - //b.AddAsyncCheck("DisabledCheck", _ => - //{ - // unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - // return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); - //}, - //parameters: new(delay: TimeSpan.FromSeconds(9), period: TimeSpan.FromSeconds(5), isEnabled: false)); }); try From 9eb7aae11e1e77897c1a586d193da87e6b507edc Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 15 Jan 2023 19:31:43 +0000 Subject: [PATCH 72/91] Update IHealthCheck.cs Clean usings --- src/HealthChecks/Abstractions/src/IHealthCheck.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/src/IHealthCheck.cs b/src/HealthChecks/Abstractions/src/IHealthCheck.cs index b7b1ab1a9b53..d806a7ecbe40 100644 --- a/src/HealthChecks/Abstractions/src/IHealthCheck.cs +++ b/src/HealthChecks/Abstractions/src/IHealthCheck.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Threading; using System.Threading.Tasks; From 78fb0524fb0e16fb9caabd36acbea266a03290bd Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 2 Feb 2023 17:01:44 +0000 Subject: [PATCH 73/91] Remove delay and period ctor --- .../src/HealthCheckRegistration.cs | 101 ++---------------- .../HealthCheckPublisherHostedServiceTest.cs | 53 +++++---- 2 files changed, 39 insertions(+), 115 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index a8b8c1557c55..8294aec323c5 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -38,40 +38,7 @@ public sealed class HealthCheckRegistration /// /// A list of tags that can be used for filtering health checks. public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, instance, failureStatus, tags, default, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// The instance. - /// - /// The that should be reported upon failure of the health check. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, instance, failureStatus, tags, timeout, default, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// The instance. - /// - /// The that should be reported upon failure of the health check. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - /// An optional representing the delay of the check. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? delay) - : this(name, instance, failureStatus, tags, timeout, delay, default) + : this(name, instance, failureStatus, tags, default) { } @@ -86,9 +53,7 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? /// /// A list of tags that can be used for filtering health checks. /// An optional representing the timeout of the check. - /// An optional representing the delay of the check. - /// An optional representing the period of the check. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout, TimeSpan? delay, TimeSpan? period) + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) { ArgumentNullThrowHelper.ThrowIfNull(name); ArgumentNullThrowHelper.ThrowIfNull(instance); @@ -103,8 +68,6 @@ public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = (_) => instance; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Delay = delay; - Period = period; } /// @@ -122,7 +85,7 @@ public HealthCheckRegistration( Func factory, HealthStatus? failureStatus, IEnumerable? tags) - : this(name, factory, failureStatus, tags, default, default, default) + : this(name, factory, failureStatus, tags, default) { } @@ -143,54 +106,6 @@ public HealthCheckRegistration( HealthStatus? failureStatus, IEnumerable? tags, TimeSpan? timeout) - : this(name, factory, failureStatus, tags, timeout, default, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// A delegate used to create the instance. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - /// An optional representing the delay of the check. - public HealthCheckRegistration( - string name, - Func factory, - HealthStatus? failureStatus, - IEnumerable? tags, - TimeSpan? timeout, - TimeSpan? delay) - : this(name, factory, failureStatus, tags, timeout, delay, default) - { - } - - /// - /// Creates a new for an existing instance. - /// - /// The health check name. - /// A delegate used to create the instance. - /// - /// The that should be reported when the health check reports a failure. If the provided value - /// is null, then will be reported. - /// - /// A list of tags that can be used for filtering health checks. - /// An optional representing the timeout of the check. - /// An optional representing the delay of the check. - /// An optional representing the period of the check. - public HealthCheckRegistration( - string name, - Func factory, - HealthStatus? failureStatus, - IEnumerable? tags, - TimeSpan? timeout, - TimeSpan? delay, - TimeSpan? period) { ArgumentNullThrowHelper.ThrowIfNull(name); ArgumentNullThrowHelper.ThrowIfNull(factory); @@ -205,8 +120,6 @@ public HealthCheckRegistration( Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); _factory = factory; Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; - Delay = delay; - Period = period; } /// @@ -246,16 +159,16 @@ public TimeSpan Timeout } /// - /// Gets the individual delay applied to the health check after the application starts before executing + /// Gets or sets the individual delay applied to the health check after the application starts before executing /// instances. The delay is applied once at startup, and does /// not apply to subsequent iterations. /// - public TimeSpan? Delay { get; } + public TimeSpan? Delay { get; set; } /// - /// Gets the individual period used for the check. + /// Gets or sets the individual period used for the check. /// - public TimeSpan? Period { get; } + public TimeSpan? Period { get; set; } /// /// Gets or sets the health check name. diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 97cbeea91a6c..87a2a1cf0c2f 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -319,19 +319,23 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, tags: null, - timeout: default, - delay: TimeSpan.FromSeconds(2), - period: TimeSpan.FromSeconds(18))); + timeout: default) + { + Delay = TimeSpan.FromSeconds(2), + Period = TimeSpan.FromSeconds(18) + }); - b.Add( + b.Add( new HealthCheckRegistration( name: "CheckDelay7Period11", instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, tags: null, - timeout: default, - delay: TimeSpan.FromSeconds(7), - period: TimeSpan.FromSeconds(11))); + timeout: default) + { + Delay = TimeSpan.FromSeconds(7), + Period = TimeSpan.FromSeconds(11) + }); b.Add( new HealthCheckRegistration( @@ -343,9 +347,11 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() }), failureStatus: null, tags: null, - timeout: default, - delay: TimeSpan.FromSeconds(9), - period: TimeSpan.FromSeconds(5))); + timeout: default) + { + Delay = TimeSpan.FromSeconds(9), + Period = TimeSpan.FromSeconds(5) + }); }); try @@ -553,7 +559,7 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() configureBuilder: b => { b.Add( - new HealthCheckRegistration( + new( name: "CheckDefault", instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, @@ -564,10 +570,11 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() name: "CheckDelay2Period18", instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, - tags: null, - timeout: default, - delay: TimeSpan.FromSeconds(2), - period: TimeSpan.FromSeconds(18))); + tags: null) + { + Delay = TimeSpan.FromSeconds(2), + Period = TimeSpan.FromSeconds(18) + }); b.Add( new HealthCheckRegistration( @@ -575,9 +582,11 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, tags: null, - timeout: default, - delay: TimeSpan.FromSeconds(7), - period: TimeSpan.FromSeconds(11))); + timeout: default) + { + Delay = TimeSpan.FromSeconds(7), + Period = TimeSpan.FromSeconds(11) + }); b.Add( new HealthCheckRegistration( @@ -589,9 +598,11 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() }), failureStatus: null, tags: null, - timeout: default, - delay: TimeSpan.FromSeconds(9), - period: TimeSpan.FromSeconds(5))); + timeout: default) + { + Delay = TimeSpan.FromSeconds(9), + Period = TimeSpan.FromSeconds(5) + }); }); try From 23794cf94c90159b877dba75d96e9a201a6e3d72 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Thu, 2 Feb 2023 17:02:58 +0000 Subject: [PATCH 74/91] Update API with setters --- src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt index f0853697c6d8..c607cf13fa6d 100644 --- a/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt @@ -1,7 +1,5 @@ #nullable enable Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.get -> System.TimeSpan? -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck! instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay) -> void -Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.HealthCheckRegistration(string! name, System.Func! factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable? tags, System.TimeSpan? timeout, System.TimeSpan? delay, System.TimeSpan? period) -> void +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.set -> void Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.get -> System.TimeSpan? +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Period.set -> void From f581089db76a0f154945d101234b7f02b8500840 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 26 Mar 2023 20:10:40 +0100 Subject: [PATCH 75/91] Restore ValidateRegistrations in DefaultHealthCheckService --- .../src/DefaultHealthCheckService.cs | 29 ++++++++++++++++++ .../test/DefaultHealthCheckServiceTest.cs | 30 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 916ef2f4749d..c2de5c1ab8a8 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -29,8 +29,37 @@ public DefaultHealthCheckService( _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + // We're specifically going out of our way to do this at startup time. We want to make sure you + // get any kind of health-check related error as early as possible. Waiting until someone + // actually tries to **run** health checks would be real baaaaad. + ValidateRegistrations(_options.Value.Registrations); + } + + private static void ValidateRegistrations(IEnumerable registrations) + { + // Scan the list for duplicate names to provide a better error if there are duplicates. + + StringBuilder? builder = null; + var distinctRegistrations = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var registration in registrations) + { + if (!distinctRegistrations.Add(registration.Name)) + { + builder ??= new StringBuilder("Duplicate health checks were registered with the name(s): "); + + builder.Append(registration.Name).Append(", "); + } + } + + if (builder is not null) + { + throw new ArgumentException(builder.ToString(0, builder.Length - 2), nameof(registrations)); + } } + public override async Task CheckHealthAsync( Func? predicate, CancellationToken cancellationToken = default) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 16b38db90b84..fd770c577b7d 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -20,6 +20,36 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; public class DefaultHealthCheckServiceTest { + [Fact] + public void Constructor_ThrowsUsefulExceptionForDuplicateNames() + { + // Arrange + // + // Doing this the old fashioned way so we can verify that the exception comes + // from the constructor. + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddOptions(); + serviceCollection.AddHealthChecks() + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); + + var services = serviceCollection.BuildServiceProvider(); + + var scopeFactory = services.GetRequiredService(); + var options = services.GetRequiredService>(); + var logger = services.GetRequiredService>(); + + // Act + var exception = Assert.Throws(() => new DefaultHealthCheckService(scopeFactory, options, logger)); + + // Assert + Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message); + } + [Fact] public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() { From 5dc3adf89580e22fa60c7d7955625b330cc3296b Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 26 Mar 2023 20:12:43 +0100 Subject: [PATCH 76/91] Refactor for perf --- .../src/HealthCheckPublisherHostedService.cs | 100 ++++++------------ .../HealthCheckPublisherHostedServiceTest.cs | 72 +++---------- 2 files changed, 45 insertions(+), 127 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 761ff988d89e..bce6224701eb 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -23,9 +23,7 @@ internal sealed partial class HealthCheckPublisherHostedService : IHostedService private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; - private readonly (TimeSpan Delay, TimeSpan Period) _defaultTimerOptions; - private readonly Dictionary<(TimeSpan Delay, TimeSpan Period), List> _healthChecksByOptions; - private Dictionary<(TimeSpan Delay, TimeSpan Period), Timer>? _timersByOptions; + private List _timers; private readonly CancellationTokenSource _stopping; private CancellationTokenSource? _runTokenSource; @@ -50,27 +48,16 @@ public HealthCheckPublisherHostedService( _publishers = publishers.ToArray(); _stopping = new CancellationTokenSource(); - - // We're specifically going out of our way to do this at startup time. We want to make sure you - // get any kind of health-check related error as early as possible. Waiting until someone - // actually tries to **run** health checks would be real baaaaad. - ValidateRegistrations(_healthCheckServiceOptions.Value.Registrations); - - _defaultTimerOptions = (_healthCheckPublisherOptions.Value.Delay, _healthCheckPublisherOptions.Value.Period); - - // Group healthcheck registrations by Delay, Period and Timeout, to build a Dictionary<(TimeSpan, TimeSpan, TimeSpan), List> - // For HCs with no Delay, Period or Timeout, we default to the publisher values - _healthChecksByOptions = _healthCheckServiceOptions.Value.Registrations.GroupBy(GetTimerOptionsOrDefault).ToDictionary(g => g.Key, g => g.ToList()); } - private (TimeSpan Delay, TimeSpan Period) GetTimerOptionsOrDefault(HealthCheckRegistration registration) + private (TimeSpan Delay, TimeSpan Period) GetTimerOptions(HealthCheckRegistration registration) { return (registration?.Delay ?? _healthCheckPublisherOptions.Value.Delay, registration?.Period ?? _healthCheckPublisherOptions.Value.Period); } internal bool IsStopping => _stopping.IsCancellationRequested; - internal bool IsTimerRunning => _timersByOptions != null; + internal bool IsTimerRunning => _timers != null; public Task StartAsync(CancellationToken cancellationToken = default) { @@ -81,7 +68,7 @@ public Task StartAsync(CancellationToken cancellationToken = default) // IMPORTANT - make sure this is the last thing that happens in this method. The timers can // fire before other code runs. - _timersByOptions = CreateTimers(_healthChecksByOptions); + _timers = CreateTimers(); return Task.CompletedTask; } @@ -102,28 +89,45 @@ public Task StopAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - if (_timersByOptions != null) + if (_timers != null) { - foreach (var timer in _timersByOptions.Values) + foreach (var timer in _timers) { timer.Dispose(); } - _timersByOptions = null; + _timers = null; } return Task.CompletedTask; } - private Dictionary<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimers(IReadOnlyDictionary<(TimeSpan Delay, TimeSpan Period), List> healthChecksByOptions) + private List CreateTimers() { - return healthChecksByOptions.Select(m => CreateTimer(m.Key)).ToDictionary(kv => kv.Key, kv => kv.Value); + var hcRegistrationsGrouped = new Dictionary<(TimeSpan Delay, TimeSpan Period), List>(); + foreach (var hc in _healthCheckServiceOptions.Value.Registrations) + { + var key = GetTimerOptions(hc); + if (!hcRegistrationsGrouped.ContainsKey(key)) + { + hcRegistrationsGrouped[key] = new List(); + } + hcRegistrationsGrouped[key].Add(hc); + } + + var timers = new List(hcRegistrationsGrouped.Count); + foreach (var m in hcRegistrationsGrouped) + { + var timer = CreateTimer(m.Key); + timers.Add(timer); + } + + return timers; } - private KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer> CreateTimer((TimeSpan Delay, TimeSpan Period) timerOptions) + private Timer CreateTimer((TimeSpan Delay, TimeSpan Period) timerOptions) { - return new KeyValuePair<(TimeSpan Delay, TimeSpan Period), Timer>( - timerOptions, + return NonCapturingTimer.Create( async (state) => { @@ -131,8 +135,7 @@ public Task StopAsync(CancellationToken cancellationToken = default) }, null, dueTime: timerOptions.Delay, - period: timerOptions.Period) - ); + period: timerOptions.Period); } // Internal for testing @@ -144,8 +147,6 @@ internal void CancelToken() // Internal for testing internal async Task RunAsync((TimeSpan Delay, TimeSpan Period)? timerOptions = null) { - timerOptions ??= _defaultTimerOptions; - var duration = ValueStopwatch.StartNew(); Logger.HealthCheckPublisherProcessingBegin(_logger); @@ -186,21 +187,9 @@ private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period) timerOptions, // Concatenate predicates - we only run HCs at the set delay, period and timeout, and that are enabled var withOptionsPredicate = (HealthCheckRegistration r) => { - // Check whether the current timer options correspond to the ones of the HC registration - var rOptions = GetTimerOptionsOrDefault(r); - var hasOptions = rOptions == timerOptions; - if (!hasOptions) - { - return false; - } - - if (_healthCheckPublisherOptions?.Value.Predicate == null) - { - return true; - } - - // Else check the user-applied predicates - return _healthCheckPublisherOptions.Value.Predicate(r); + // First check whether the current timer options correspond to the current registration, + // and then check the user-defined predicate if any. + return (GetTimerOptions(r) == timerOptions) && (_healthCheckPublisherOptions?.Value.Predicate ?? (_ => true))(r); }; // The health checks service does it's own logging, and doesn't throw exceptions. @@ -244,29 +233,6 @@ private async Task RunPublisherAsync(IHealthCheckPublisher publisher, HealthRepo } } - private static void ValidateRegistrations(IEnumerable registrations) - { - // Scan the list for duplicate names to provide a better error if there are duplicates. - - StringBuilder? builder = null; - var distinctRegistrations = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var registration in registrations) - { - if (!distinctRegistrations.Add(registration.Name)) - { - builder ??= new StringBuilder("Duplicate health checks were registered with the name(s): "); - - builder.Append(registration.Name).Append(", "); - } - } - - if (builder is not null) - { - throw new ArgumentException(builder.ToString(0, builder.Length - 2), nameof(registrations)); - } - } - internal static class EventIds { public const int HealthCheckPublisherProcessingBeginId = 100; diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 87a2a1cf0c2f..4bdcb6be29d2 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -37,56 +37,6 @@ private static class HealthCheckPublisherEventIds public static readonly EventId HealthCheckPublisherTimeout = new EventId(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherTimeoutId, HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherTimeoutName); } - [Fact] - public void Constructor_ThrowsUsefulExceptionForDuplicateNames() - { - // Arrange - // - // Doing this the old fashioned way so we can verify that the exception comes - // from the constructor. - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddOptions(); - serviceCollection.AddLogging(); - serviceCollection.AddHealthChecks() - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) - .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); - - // Choosing big values for tests to make sure that we're not dependent on the defaults. - // All of the tests that rely on the timer will set their own values for speed. - serviceCollection.Configure(options => - { - options.Delay = TimeSpan.FromMinutes(5); - options.Period = TimeSpan.FromMinutes(5); - options.Timeout = TimeSpan.FromMinutes(5); - }); - - var services = serviceCollection.BuildServiceProvider(); - - var healthCheckService = services.GetRequiredService(); - var healthCheckServiceOptions = services.GetRequiredService>(); - var healthCheckPublisherOptions = services.GetRequiredService>(); - var logger = services.GetRequiredService>(); - - var publishers = new IHealthCheckPublisher[] - { - }; - - // Act - var exception = Assert.Throws(() => new HealthCheckPublisherHostedService( - healthCheckService, - healthCheckServiceOptions, - healthCheckPublisherOptions, - logger, - publishers)); - - // Assert - Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message); - } - [Fact] public async Task StartAsync_WithoutPublishers_DoesNotStartTimer() { @@ -205,7 +155,7 @@ public async Task StopAsync_CancelsExecution() await service.StartAsync(); // Start execution - var running = service.RunAsync(); + var running = RunServiceAsync(service); // Wait for the publisher to see the cancellation token await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); @@ -251,7 +201,7 @@ public async Task RunAsync_WaitsForCompletion_Single() await service.StartAsync(); // Act - var running = service.RunAsync(); + var running = RunServiceAsync(service); await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); @@ -359,7 +309,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() await service.StartAsync(); // Act - var running = service.RunAsync(); + var running = RunServiceAsync(service); await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); @@ -413,7 +363,7 @@ public async Task RunAsync_WaitsForCompletion_Multiple() await service.StartAsync(); // Act - var running = service.RunAsync(); + var running = RunServiceAsync(service); await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); await publishers[1].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); @@ -462,7 +412,7 @@ public async Task RunAsync_PublishersCanTimeout() await service.StartAsync(); // Act - var running = service.RunAsync(); + var running = RunServiceAsync(service); await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); @@ -519,7 +469,7 @@ public async Task RunAsync_CanFilterHealthChecks() await service.StartAsync(); // Act - await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(10)); + await RunServiceAsync(service).TimeoutAfter(TimeSpan.FromSeconds(10)); // Assert for (var i = 0; i < publishers.Length; i++) @@ -610,7 +560,7 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() await service.StartAsync(); // Act - await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(20)); + await RunServiceAsync(service).TimeoutAfter(TimeSpan.FromSeconds(20)); await unblockDelayedCheck.Task; @@ -650,7 +600,7 @@ public async Task RunAsync_HandlesExceptions() await service.StartAsync(); // Act - await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(10)); + await RunServiceAsync(service).TimeoutAfter(TimeSpan.FromSeconds(10)); } finally @@ -694,7 +644,7 @@ public async Task RunAsync_HandlesExceptions_Multiple() await service.StartAsync(); // Act - await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(10)); + await RunServiceAsync(service).TimeoutAfter(TimeSpan.FromSeconds(10)); } finally @@ -733,7 +683,7 @@ private HealthCheckPublisherHostedService CreateService( options.Delay = TimeSpan.FromMinutes(5); options.Period = TimeSpan.FromMinutes(5); options.Timeout = TimeSpan.FromMinutes(5); - }); + }); if (publishers != null) { @@ -757,6 +707,8 @@ private HealthCheckPublisherHostedService CreateService( return services.GetServices().OfType().Single(); } + private Task RunServiceAsync(HealthCheckPublisherHostedService service) => service.RunAsync((TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5))); + private static async Task AssertCanceledAsync(CancellationToken cancellationToken) { await Assert.ThrowsAsync(() => Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)); From cdc01a33267f097c670c5865316cbd81cec32630 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 26 Mar 2023 20:34:21 +0100 Subject: [PATCH 77/91] Address CI failed checks --- .../HealthChecks/src/DefaultHealthCheckService.cs | 1 - .../src/HealthCheckPublisherHostedService.cs | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index c2de5c1ab8a8..ff4fea412a25 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -59,7 +59,6 @@ private static void ValidateRegistrations(IEnumerable r } } - public override async Task CheckHealthAsync( Func? predicate, CancellationToken cancellationToken = default) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index bce6224701eb..f757009244d5 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -23,7 +23,7 @@ internal sealed partial class HealthCheckPublisherHostedService : IHostedService private readonly IOptions _healthCheckPublisherOptions; private readonly ILogger _logger; private readonly IHealthCheckPublisher[] _publishers; - private List _timers; + private List? _timers; private readonly CancellationTokenSource _stopping; private CancellationTokenSource? _runTokenSource; @@ -107,12 +107,12 @@ private List CreateTimers() var hcRegistrationsGrouped = new Dictionary<(TimeSpan Delay, TimeSpan Period), List>(); foreach (var hc in _healthCheckServiceOptions.Value.Registrations) { - var key = GetTimerOptions(hc); - if (!hcRegistrationsGrouped.ContainsKey(key)) + var timerOptions = GetTimerOptions(hc); + if (!hcRegistrationsGrouped.ContainsKey(timerOptions)) { - hcRegistrationsGrouped[key] = new List(); + hcRegistrationsGrouped[timerOptions] = new List(); } - hcRegistrationsGrouped[key].Add(hc); + hcRegistrationsGrouped[timerOptions].Add(hc); } var timers = new List(hcRegistrationsGrouped.Count); @@ -145,7 +145,7 @@ internal void CancelToken() } // Internal for testing - internal async Task RunAsync((TimeSpan Delay, TimeSpan Period)? timerOptions = null) + internal async Task RunAsync((TimeSpan Delay, TimeSpan Period) timerOptions = default) { var duration = ValueStopwatch.StartNew(); Logger.HealthCheckPublisherProcessingBegin(_logger); @@ -159,7 +159,7 @@ internal async Task RunAsync((TimeSpan Delay, TimeSpan Period)? timerOptions = n _runTokenSource = cancellation; cancellation.CancelAfter(timeout); - await RunAsyncCore(timerOptions.Value, cancellation.Token).ConfigureAwait(false); + await RunAsyncCore(timerOptions, cancellation.Token).ConfigureAwait(false); Logger.HealthCheckPublisherProcessingEnd(_logger, duration.GetElapsedTime()); } From 21785ecc3a183ad94057f547f6a2035e558ca2f1 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 26 Mar 2023 20:44:43 +0100 Subject: [PATCH 78/91] Restore DefaultHealthCheckService --- .../src/DefaultHealthCheckService.cs | 49 ++++++++++--------- .../test/DefaultHealthCheckServiceTest.cs | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index ff4fea412a25..edc12177136a 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -35,30 +35,6 @@ public DefaultHealthCheckService( // actually tries to **run** health checks would be real baaaaad. ValidateRegistrations(_options.Value.Registrations); } - - private static void ValidateRegistrations(IEnumerable registrations) - { - // Scan the list for duplicate names to provide a better error if there are duplicates. - - StringBuilder? builder = null; - var distinctRegistrations = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var registration in registrations) - { - if (!distinctRegistrations.Add(registration.Name)) - { - builder ??= new StringBuilder("Duplicate health checks were registered with the name(s): "); - - builder.Append(registration.Name).Append(", "); - } - } - - if (builder is not null) - { - throw new ArgumentException(builder.ToString(0, builder.Length - 2), nameof(registrations)); - } - } - public override async Task CheckHealthAsync( Func? predicate, CancellationToken cancellationToken = default) @@ -181,6 +157,29 @@ private async Task RunCheckAsync(HealthCheckRegistration regi } } + private static void ValidateRegistrations(IEnumerable registrations) + { + // Scan the list for duplicate names to provide a better error if there are duplicates. + + StringBuilder? builder = null; + var distinctRegistrations = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var registration in registrations) + { + if (!distinctRegistrations.Add(registration.Name)) + { + builder ??= new StringBuilder("Duplicate health checks were registered with the name(s): "); + + builder.Append(registration.Name).Append(", "); + } + } + + if (builder is not null) + { + throw new ArgumentException(builder.ToString(0, builder.Length - 2), nameof(registrations)); + } + } + internal static class EventIds { public const int HealthCheckProcessingBeginId = 100; @@ -219,6 +218,7 @@ public static void HealthCheckProcessingEnd(ILogger logger, HealthStatus status, private const string HealthCheckEndText = "Health check {HealthCheckName} with status {HealthStatus} completed after {ElapsedMilliseconds}ms with message '{HealthCheckDescription}'"; #pragma warning disable SYSLIB1006 +#pragma warning disable SYSLIB1025 [LoggerMessage(EventIds.HealthCheckEndId, LogLevel.Debug, HealthCheckEndText, EventName = EventIds.HealthCheckEndName)] private static partial void HealthCheckEndHealthy(ILogger logger, string HealthCheckName, HealthStatus HealthStatus, double ElapsedMilliseconds, string? HealthCheckDescription); @@ -227,6 +227,7 @@ public static void HealthCheckProcessingEnd(ILogger logger, HealthStatus status, [LoggerMessage(EventIds.HealthCheckEndId, LogLevel.Error, HealthCheckEndText, EventName = EventIds.HealthCheckEndName)] private static partial void HealthCheckEndUnhealthy(ILogger logger, string HealthCheckName, HealthStatus HealthStatus, double ElapsedMilliseconds, string? HealthCheckDescription, Exception? exception); +#pragma warning restore SYSLIB1025 #pragma warning restore SYSLIB1006 public static void HealthCheckEnd(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry, TimeSpan duration) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index fd770c577b7d..735f228898e6 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks; public class DefaultHealthCheckServiceTest { - [Fact] + [Fact] public void Constructor_ThrowsUsefulExceptionForDuplicateNames() { // Arrange From 70c2f0606b76f9cc62c27bcc2e735b966c46c0ef Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 26 Mar 2023 20:50:27 +0100 Subject: [PATCH 79/91] Remove SYSLIB1025 --- src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index edc12177136a..45e8a8963d72 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -218,7 +218,6 @@ public static void HealthCheckProcessingEnd(ILogger logger, HealthStatus status, private const string HealthCheckEndText = "Health check {HealthCheckName} with status {HealthStatus} completed after {ElapsedMilliseconds}ms with message '{HealthCheckDescription}'"; #pragma warning disable SYSLIB1006 -#pragma warning disable SYSLIB1025 [LoggerMessage(EventIds.HealthCheckEndId, LogLevel.Debug, HealthCheckEndText, EventName = EventIds.HealthCheckEndName)] private static partial void HealthCheckEndHealthy(ILogger logger, string HealthCheckName, HealthStatus HealthStatus, double ElapsedMilliseconds, string? HealthCheckDescription); @@ -227,7 +226,6 @@ public static void HealthCheckProcessingEnd(ILogger logger, HealthStatus status, [LoggerMessage(EventIds.HealthCheckEndId, LogLevel.Error, HealthCheckEndText, EventName = EventIds.HealthCheckEndName)] private static partial void HealthCheckEndUnhealthy(ILogger logger, string HealthCheckName, HealthStatus HealthStatus, double ElapsedMilliseconds, string? HealthCheckDescription, Exception? exception); -#pragma warning restore SYSLIB1025 #pragma warning restore SYSLIB1006 public static void HealthCheckEnd(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry, TimeSpan duration) From 664ad8819d64e775da4ca9cb5cbc62bf694d848d Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 26 Mar 2023 22:15:13 +0100 Subject: [PATCH 80/91] Restore HealthCheckServiceOptions --- src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs b/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs index b65b44cf8e8e..3e2e9e89d738 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; From e18b5fe8d1f44c815ab1243709dc382d12b23c55 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 27 Mar 2023 11:39:36 +0100 Subject: [PATCH 81/91] Simplify dict registrations to hashset --- .../src/HealthCheckPublisherHostedService.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index f757009244d5..4f4027e48ee3 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -104,21 +104,17 @@ public Task StopAsync(CancellationToken cancellationToken = default) private List CreateTimers() { - var hcRegistrationsGrouped = new Dictionary<(TimeSpan Delay, TimeSpan Period), List>(); + var delayPeriodGroups = new HashSet<(TimeSpan Delay, TimeSpan Period)>(); foreach (var hc in _healthCheckServiceOptions.Value.Registrations) { var timerOptions = GetTimerOptions(hc); - if (!hcRegistrationsGrouped.ContainsKey(timerOptions)) - { - hcRegistrationsGrouped[timerOptions] = new List(); - } - hcRegistrationsGrouped[timerOptions].Add(hc); + delayPeriodGroups.Add(timerOptions); } - var timers = new List(hcRegistrationsGrouped.Count); - foreach (var m in hcRegistrationsGrouped) + var timers = new List(delayPeriodGroups.Count); + foreach (var m in delayPeriodGroups) { - var timer = CreateTimer(m.Key); + var timer = CreateTimer(m); timers.Add(timer); } @@ -184,7 +180,7 @@ private async Task RunAsyncCore((TimeSpan Delay, TimeSpan Period) timerOptions, // Forcibly yield - we want to unblock the timer thread. await Task.Yield(); - // Concatenate predicates - we only run HCs at the set delay, period and timeout, and that are enabled + // Concatenate predicates - we only run HCs at the set delay and period var withOptionsPredicate = (HealthCheckRegistration r) => { // First check whether the current timer options correspond to the current registration, From ba7089df59b09c0cd96f7dd5c9759d5229c3a615 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 27 Mar 2023 11:41:35 +0100 Subject: [PATCH 82/91] Rename to group --- .../HealthChecks/src/HealthCheckPublisherHostedService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 4f4027e48ee3..13ae00c9a738 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -112,9 +112,9 @@ private List CreateTimers() } var timers = new List(delayPeriodGroups.Count); - foreach (var m in delayPeriodGroups) + foreach (var group in delayPeriodGroups) { - var timer = CreateTimer(m); + var timer = CreateTimer(group); timers.Add(timer); } From d602199b1b428a7f238dd1c51ce3dd528e6e8e9b Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 11:30:05 +0100 Subject: [PATCH 83/91] Remove timerOptions default --- .../HealthChecks/src/HealthCheckPublisherHostedService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs index 13ae00c9a738..894a40b5e63c 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -141,7 +141,7 @@ internal void CancelToken() } // Internal for testing - internal async Task RunAsync((TimeSpan Delay, TimeSpan Period) timerOptions = default) + internal async Task RunAsync((TimeSpan Delay, TimeSpan Period) timerOptions) { var duration = ValueStopwatch.StartNew(); Logger.HealthCheckPublisherProcessingBegin(_logger); From 9e67bee8e2891700df1651a3b00871a3f4f04fba Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 11:33:30 +0100 Subject: [PATCH 84/91] Fix indentation Co-authored-by: Stephen Halter --- .../test/HealthCheckPublisherHostedServiceTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 4bdcb6be29d2..bc51c7ae13be 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -257,11 +257,11 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() var service = CreateService(publishers, configureBuilder: b => { b.Add( - new HealthCheckRegistration( - name: "CheckDefault", - instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), - failureStatus: null, - tags: null)); + new HealthCheckRegistration( + name: "CheckDefault", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null)); b.Add( new HealthCheckRegistration( From 273371b1697ec3b0211ca96cb57667d8dd0eebb0 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 11:34:06 +0100 Subject: [PATCH 85/91] Fix indentation Co-authored-by: Stephen Halter --- .../HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index bc51c7ae13be..68e93e67730d 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -275,7 +275,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() Period = TimeSpan.FromSeconds(18) }); - b.Add( + b.Add( new HealthCheckRegistration( name: "CheckDelay7Period11", instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), From f5a3f55eb03a51b640e5c91db8fad1a9fcdde3b7 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 11:34:43 +0100 Subject: [PATCH 86/91] Fix indentation Co-authored-by: Stephen Halter --- .../HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 68e93e67730d..feb3b26bf59a 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -295,7 +295,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); }), - failureStatus: null, + failureStatus: null, tags: null, timeout: default) { From a8385ecf51c912430a3919a3ae0f9b523e090899 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 11:41:41 +0100 Subject: [PATCH 87/91] Update delay, periods by factor 10 --- .../HealthCheckPublisherHostedServiceTest.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index feb3b26bf59a..5c947d7511d7 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -271,8 +271,8 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(2), - Period = TimeSpan.FromSeconds(18) + Delay = TimeSpan.FromSeconds(.2), + Period = TimeSpan.FromSeconds(1.8) }); b.Add( @@ -283,8 +283,8 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(7), - Period = TimeSpan.FromSeconds(11) + Delay = TimeSpan.FromSeconds(.7), + Period = TimeSpan.FromSeconds(1.1) }); b.Add( @@ -299,8 +299,8 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(9), - Period = TimeSpan.FromSeconds(5) + Delay = TimeSpan.FromSeconds(.9), + Period = TimeSpan.FromSeconds(.5) }); }); @@ -522,8 +522,8 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() failureStatus: null, tags: null) { - Delay = TimeSpan.FromSeconds(2), - Period = TimeSpan.FromSeconds(18) + Delay = TimeSpan.FromSeconds(.2), + Period = TimeSpan.FromSeconds(1.8) }); b.Add( @@ -534,8 +534,8 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(7), - Period = TimeSpan.FromSeconds(11) + Delay = TimeSpan.FromSeconds(.7), + Period = TimeSpan.FromSeconds(1.1) }); b.Add( @@ -550,8 +550,8 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(9), - Period = TimeSpan.FromSeconds(5) + Delay = TimeSpan.FromSeconds(.9), + Period = TimeSpan.FromSeconds(.5) }); }); From 1fda48d45833eb524de19ff402ad0a6cb14da082 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 11:50:59 +0100 Subject: [PATCH 88/91] Update publishers to single object --- .../HealthCheckPublisherHostedServiceTest.cs | 62 +++++++------------ 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 5c947d7511d7..3d9ef31c0352 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -143,12 +143,9 @@ public async Task StopAsync_CancelsExecution() // Arrange var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var publishers = new TestPublisher[] - { - new TestPublisher() { Wait = unblock.Task, } - }; + var publisher = new TestPublisher() { Wait = unblock.Task, }; - var service = CreateService(publishers); + var service = CreateService(new[] { publisher }); try { @@ -158,14 +155,14 @@ public async Task StopAsync_CancelsExecution() var running = RunServiceAsync(service); // Wait for the publisher to see the cancellation token - await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); - Assert.Single(publishers[0].Entries); + await publisher.Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + Assert.Single(publisher.Entries); // Act await service.StopAsync(); // Trigger cancellation // Assert - await AssertCanceledAsync(publishers[0].Entries[0].cancellationToken); + await AssertCanceledAsync(publisher.Entries[0].cancellationToken); Assert.False(service.IsTimerRunning); Assert.True(service.IsStopping); @@ -189,12 +186,9 @@ public async Task RunAsync_WaitsForCompletion_Single() var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var publishers = new TestPublisher[] - { - new TestPublisher() { Wait = unblock.Task, }, - }; + var publisher = new TestPublisher() { Wait = unblock.Task, }; - var service = CreateService(publishers, sink: sink); + var service = CreateService(new[] { publisher }, sink: sink); try { @@ -203,7 +197,7 @@ public async Task RunAsync_WaitsForCompletion_Single() // Act var running = RunServiceAsync(service); - await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await publisher.Started.TimeoutAfter(TimeSpan.FromSeconds(10)); unblock.SetResult(null); @@ -213,11 +207,8 @@ public async Task RunAsync_WaitsForCompletion_Single() Assert.True(service.IsTimerRunning); Assert.False(service.IsStopping); - for (var i = 0; i < publishers.Length; i++) - { - var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); - } + var report = Assert.Single(publisher.Entries).report; + Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); } finally { @@ -249,12 +240,9 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var unblockDelayedCheck = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var publishers = new TestPublisher[] - { - new TestPublisher() { Wait = unblock.Task, }, - }; + var publisher = new TestPublisher() { Wait = unblock.Task, }; - var service = CreateService(publishers, configureBuilder: b => + var service = CreateService(new[] { publisher }, configureBuilder: b => { b.Add( new HealthCheckRegistration( @@ -311,7 +299,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() // Act var running = RunServiceAsync(service); - await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await publisher.Started.TimeoutAfter(TimeSpan.FromSeconds(10)); unblock.SetResult(null); @@ -323,14 +311,11 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() Assert.True(service.IsTimerRunning); Assert.False(service.IsStopping); - for (var i = 0; i < publishers.Length; i++) - { - Assert.True(publishers[i].Entries.Count == 4); - var entries = publishers[i].Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); - Assert.Equal( - new[] { "CheckDefault", "CheckDelay2Period18", "CheckDelay7Period11", "CheckDelay9Period5" }, - entries); - } + Assert.True(publisher.Entries.Count == 4); + var entries = publisher.Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); + Assert.Equal( + new[] { "CheckDefault", "CheckDelay2Period18", "CheckDelay7Period11", "CheckDelay9Period5" }, + entries); } finally { @@ -400,12 +385,9 @@ public async Task RunAsync_PublishersCanTimeout() var sink = new TestSink(); var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var publishers = new TestPublisher[] - { - new TestPublisher() { Wait = unblock.Task, }, - }; + var publisher = new TestPublisher() { Wait = unblock.Task, }; - var service = CreateService(publishers, sink: sink); + var service = CreateService(new[] { publisher }, sink: sink); try { @@ -414,11 +396,11 @@ public async Task RunAsync_PublishersCanTimeout() // Act var running = RunServiceAsync(service); - await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await publisher.Started.TimeoutAfter(TimeSpan.FromSeconds(10)); service.CancelToken(); - await AssertCanceledAsync(publishers[0].Entries[0].cancellationToken); + await AssertCanceledAsync(publisher.Entries[0].cancellationToken); unblock.SetResult(null); From e7f9e042103ad1677ff49653b774ef2afff9a5c6 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 11:53:35 +0100 Subject: [PATCH 89/91] Rsstore test periods --- .../test/HealthCheckPublisherHostedServiceTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 3d9ef31c0352..7f44d270b92a 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -260,7 +260,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() timeout: default) { Delay = TimeSpan.FromSeconds(.2), - Period = TimeSpan.FromSeconds(1.8) + Period = TimeSpan.FromSeconds(18) }); b.Add( @@ -272,7 +272,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() timeout: default) { Delay = TimeSpan.FromSeconds(.7), - Period = TimeSpan.FromSeconds(1.1) + Period = TimeSpan.FromSeconds(11) }); b.Add( @@ -288,7 +288,7 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() timeout: default) { Delay = TimeSpan.FromSeconds(.9), - Period = TimeSpan.FromSeconds(.5) + Period = TimeSpan.FromSeconds(5) }); }); @@ -505,7 +505,7 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() tags: null) { Delay = TimeSpan.FromSeconds(.2), - Period = TimeSpan.FromSeconds(1.8) + Period = TimeSpan.FromSeconds(18) }); b.Add( @@ -517,7 +517,7 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() timeout: default) { Delay = TimeSpan.FromSeconds(.7), - Period = TimeSpan.FromSeconds(1.1) + Period = TimeSpan.FromSeconds(11) }); b.Add( @@ -533,7 +533,7 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() timeout: default) { Delay = TimeSpan.FromSeconds(.9), - Period = TimeSpan.FromSeconds(.5) + Period = TimeSpan.FromSeconds(5) }); }); From 17a8e6b534a3f7278503231e2c9b2248458f36c2 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Tue, 28 Mar 2023 23:17:12 +0100 Subject: [PATCH 90/91] Update delay, period for blocking task --- .../HealthCheckPublisherHostedServiceTest.cs | 265 ++++++++++++------ 1 file changed, 180 insertions(+), 85 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 7f44d270b92a..d31458320264 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -253,17 +253,58 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() b.Add( new HealthCheckRegistration( - name: "CheckDelay2Period18", + name: "CheckDelay1Period9", instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(.2), + Delay = TimeSpan.FromSeconds(1), + Period = TimeSpan.FromSeconds(9) + }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay1Period9_1", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(1), + Period = TimeSpan.FromSeconds(9) + }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay1Period18", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(1), + Period = TimeSpan.FromSeconds(18) + }); + + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay2Period18", + instance: new DelegateHealthCheck(_ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock 2s delayed check + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + }), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(2), Period = TimeSpan.FromSeconds(18) }); - b.Add( + b.Add( new HealthCheckRegistration( name: "CheckDelay7Period11", instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), @@ -271,51 +312,68 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(.7), + Delay = TimeSpan.FromSeconds(7), Period = TimeSpan.FromSeconds(11) }); b.Add( new HealthCheckRegistration( name: "CheckDelay9Period5", - instance: new DelegateHealthCheck(_ => - { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); - }), + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(.9), + Delay = TimeSpan.FromSeconds(9), Period = TimeSpan.FromSeconds(5) }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay10Period8", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(10), + Period = TimeSpan.FromSeconds(8) + }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay10Period9", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(10), + Period = TimeSpan.FromSeconds(9) + }); }); try { - await service.StartAsync(); - - // Act var running = RunServiceAsync(service); await publisher.Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await Task.Yield(); + Assert.False(running.IsCompleted); + unblock.SetResult(null); - await running.TimeoutAfter(TimeSpan.FromSeconds(20)); + await running.TimeoutAfter(TimeSpan.FromSeconds(10)); - await Task.WhenAll(unblock.Task, unblockDelayedCheck.Task); + // The timer hasn't started yet. Only the default 5 minute registration is run by RunServiceAsync. + Assert.Equal("CheckDefault", Assert.Single(Assert.Single(publisher.Entries).report.Entries.Keys)); + + await service.StartAsync(); + await unblockDelayedCheck.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); - // Assert Assert.True(service.IsTimerRunning); Assert.False(service.IsStopping); - - Assert.True(publisher.Entries.Count == 4); - var entries = publisher.Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); - Assert.Equal( - new[] { "CheckDefault", "CheckDelay2Period18", "CheckDelay7Period11", "CheckDelay9Period5" }, - entries); } finally { @@ -323,6 +381,13 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() Assert.False(service.IsTimerRunning); Assert.True(service.IsStopping); } + + // Assert - after stop + var entries = publisher.Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); + Assert.Contains("CheckDefault", entries); + Assert.Contains("CheckDelay1Period18", entries); + Assert.Contains("CheckDelay1Period9", entries); + Assert.Contains("CheckDelay1Period9_1", entries); } // Not testing logs here to avoid differences in logging order @@ -433,43 +498,6 @@ public async Task RunAsync_PublishersCanTimeout() [Fact] public async Task RunAsync_CanFilterHealthChecks() - { - // Arrange - var publishers = new TestPublisher[] - { - new TestPublisher(), - new TestPublisher(), - }; - - var service = CreateService(publishers, configurePublisherOptions: (options) => - { - options.Predicate = (r) => r.Name == "one"; - }); - - try - { - await service.StartAsync(); - - // Act - await RunServiceAsync(service).TimeoutAfter(TimeSpan.FromSeconds(10)); - - // Assert - for (var i = 0; i < publishers.Length; i++) - { - var report = Assert.Single(publishers[i].Entries).report; - Assert.Equal(new[] { "one", }, report.Entries.Keys.OrderBy(k => k)); - } - } - finally - { - await service.StopAsync(); - Assert.False(service.IsTimerRunning); - Assert.True(service.IsStopping); - } - } - - [Fact] - public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() { // Arrange const string HealthyMessage = "Everything is A-OK"; @@ -486,25 +514,67 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() publishers, configurePublisherOptions: (options) => { - options.Predicate = (r) => r.Name.Contains("Delay"); + options.Predicate = (r) => r.Name.Contains("Delay") && !r.Name.Contains("_2"); + options.Delay = TimeSpan.Zero; }, configureBuilder: b => { b.Add( - new( - name: "CheckDefault", + new HealthCheckRegistration( + name: "CheckDefault", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null)); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay1Period9", instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), failureStatus: null, - tags: null)); + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(1), + Period = TimeSpan.FromSeconds(9) + }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay1Period9_1", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(1), + Period = TimeSpan.FromSeconds(9) + }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay1Period9_2", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(1), + Period = TimeSpan.FromSeconds(9) + }); b.Add( new HealthCheckRegistration( name: "CheckDelay2Period18", - instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + instance: new DelegateHealthCheck(_ => + { + unblockDelayedCheck.TrySetResult(null); // Unblock 2s delayed check + return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); + }), failureStatus: null, - tags: null) + tags: null, + timeout: default) { - Delay = TimeSpan.FromSeconds(.2), + Delay = TimeSpan.FromSeconds(2), Period = TimeSpan.FromSeconds(18) }); @@ -516,24 +586,44 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() tags: null, timeout: default) { - Delay = TimeSpan.FromSeconds(.7), + Delay = TimeSpan.FromSeconds(7), Period = TimeSpan.FromSeconds(11) }); b.Add( new HealthCheckRegistration( - name: "CheckDelay9Period5", - instance: new DelegateHealthCheck(_ => - { - unblockDelayedCheck.TrySetResult(null); // Unblock last delayed check - return Task.FromResult(HealthCheckResult.Healthy(HealthyMessage)); - }), - failureStatus: null, - tags: null, - timeout: default) + name: "CheckDelay9Period5", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) { - Delay = TimeSpan.FromSeconds(.9), - Period = TimeSpan.FromSeconds(5) + Delay = TimeSpan.FromSeconds(9), + Period = TimeSpan.FromSeconds(5) + }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay10Period8", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(10), + Period = TimeSpan.FromSeconds(8) + }); + + b.Add( + new HealthCheckRegistration( + name: "CheckDelay10Period9", + instance: new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage))), + failureStatus: null, + tags: null, + timeout: default) + { + Delay = TimeSpan.FromSeconds(10), + Period = TimeSpan.FromSeconds(9) }); }); @@ -542,19 +632,15 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() await service.StartAsync(); // Act - await RunServiceAsync(service).TimeoutAfter(TimeSpan.FromSeconds(20)); - - await unblockDelayedCheck.Task; + await unblockDelayedCheck.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); // Assert for (var i = 0; i < publishers.Length; i++) { var entries = publishers[i].Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); - Assert.True(entries.Count() == 3); - Assert.Equal( - new[] { "CheckDelay2Period18", "CheckDelay7Period11", "CheckDelay9Period5" }, - entries); + Assert.Contains("CheckDelay1Period9", entries); + Assert.Contains("CheckDelay1Period9_1", entries); } } finally @@ -563,6 +649,15 @@ public async Task RunAsync_CanFilterHealthChecks_RegistrationParameters() Assert.False(service.IsTimerRunning); Assert.True(service.IsStopping); } + + // Assert - after stop + for (var i = 0; i < publishers.Length; i++) + { + var entries = publishers[i].Entries.SelectMany(e => e.report.Entries.Select(e2 => e2.Key)).OrderBy(k => k).ToArray(); + + Assert.Contains("CheckDelay1Period9", entries); + Assert.Contains("CheckDelay1Period9_1", entries); + } } [Fact] From 0e8dd9dc44c75e0c6988ab5b49c8e8a74cf93c29 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Wed, 29 Mar 2023 11:44:05 +1100 Subject: [PATCH 91/91] Remove extra blank line. --- .../HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index d31458320264..2e6d01658f24 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -287,7 +287,6 @@ public async Task RunAsync_WaitsForCompletion_Single_RegistrationParameters() Period = TimeSpan.FromSeconds(18) }); - b.Add( new HealthCheckRegistration( name: "CheckDelay2Period18",