From 7960bde7dada91a3c8bd51c971dcd6973d5aadde Mon Sep 17 00:00:00 2001 From: Nathanael Marchand Date: Tue, 19 Feb 2019 10:23:49 +0100 Subject: [PATCH] Implement Timeout \n\nCommit migrated from https://github.com/dotnet/extensions/commit/50593c9336a6746bcafa6325fc5ac772bb07fc65 --- ...ealthChecks.Abstractions.netstandard2.0.cs | 3 + .../src/HealthCheckRegistration.cs | 71 +++++++++++- ...Diagnostics.HealthChecks.netstandard2.0.cs | 19 +++- .../src/DefaultHealthCheckService.cs | 34 +++++- .../HealthChecksBuilderAddCheckExtensions.cs | 106 +++++++++++++++++- .../HealthChecksBuilderDelegateExtensions.cs | 98 ++++++++++++++-- .../test/DefaultHealthCheckServiceTest.cs | 26 +++++ 7 files changed, 332 insertions(+), 25 deletions(-) diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs index 7fb5beb0e9..9ab497257e 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs @@ -11,11 +11,14 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks public sealed partial class HealthCheckRegistration { public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } public System.Func Factory { get { throw null; } set { } } public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus FailureStatus { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Name { get { throw null; } set { } } public System.Collections.Generic.ISet Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan Timeout { get { throw null; } set { } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public partial struct HealthCheckResult diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index 9291c38846..8ee11e3195 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -24,6 +24,22 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { 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) + { + } /// /// Creates a new for an existing instance. @@ -35,7 +51,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// 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) + /// An optional representing the timeout of the check. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable tags, TimeSpan? timeout) { if (name == null) { @@ -47,10 +64,16 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks 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; } /// @@ -68,6 +91,27 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Func factory, HealthStatus? failureStatus, IEnumerable tags) + : this(name, factory, failureStatus, tags, 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) { if (name == null) { @@ -79,10 +123,16 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks 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; } /// @@ -107,6 +157,23 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// 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 or sets the health check name. /// diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs index 58bdc80602..a23961efdd 100644 --- a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs @@ -5,18 +5,25 @@ namespace Microsoft.Extensions.DependencyInjection { public static partial class HealthChecksBuilderAddCheckExtensions { - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder 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) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder 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) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } } public static partial class HealthChecksBuilderDelegateExtensions { - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } } public static partial class HealthCheckServiceCollectionExtensions { diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index da8c00a5aa..56ce966d18 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -91,9 +91,21 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Log.HealthCheckBegin(_logger, registration); HealthReportEntry entry; + CancellationTokenSource timeoutCancellationTokenSource = null; try { - var result = await healthCheck.CheckHealthAsync(context, cancellationToken); + HealthCheckResult result; + + var checkCancellationToken = cancellationToken; + if (registration.Timeout > TimeSpan.Zero) + { + timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + timeoutCancellationTokenSource.CancelAfter(registration.Timeout); + checkCancellationToken = timeoutCancellationTokenSource.Token; + } + + result = await healthCheck.CheckHealthAsync(context, checkCancellationToken); + var duration = stopwatch.GetElapsedTime(); entry = new HealthReportEntry( @@ -107,7 +119,20 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Log.HealthCheckData(_logger, registration, entry); } - // Allow cancellation to propagate. + catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested) + { + var duration = stopwatch.GetElapsedTime(); + entry = new HealthReportEntry( + status: HealthStatus.Unhealthy, + description: "A timeout occured while running check.", + duration: duration, + exception: ex, + data: null); + + Log.HealthCheckError(_logger, registration, ex, duration); + } + + // Allow cancellation to propagate if it's not a timeout. catch (Exception ex) when (ex as OperationCanceledException == null) { var duration = stopwatch.GetElapsedTime(); @@ -121,6 +146,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Log.HealthCheckError(_logger, registration, ex, duration); } + finally + { + timeoutCancellationTokenSource?.Dispose(); + } + return entry; } } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 9508889054..74ee30290c 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -24,12 +24,37 @@ namespace Microsoft.Extensions.DependencyInjection /// /// A list of tags that can be used to filter health checks. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + IHealthCheck instance, + HealthStatus? failureStatus, + IEnumerable tags) + { + return AddCheck(builder, name, instance, failureStatus, tags, 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 = null, - IEnumerable tags = null) + IEnumerable tags = null, + TimeSpan? timeout = null) { if (builder == null) { @@ -46,7 +71,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout)); } /// @@ -63,15 +88,45 @@ namespace Microsoft.Extensions.DependencyInjection /// The . /// /// This method will use to create the health check - /// instance when needed. If a service of type is registred in the dependency injection container - /// with any liftime it will be used. Otherwise an instance of type will be constructed with + /// 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. + /// + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + IEnumerable tags) where T : class, IHealthCheck + { + return AddCheck(builder, name, failureStatus, tags, 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( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus = null, - IEnumerable tags = null) where T : class, IHealthCheck + IEnumerable tags = null, + TimeSpan? timeout = null) where T : class, IHealthCheck { if (builder == null) { @@ -83,7 +138,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.GetServiceOrCreateInstance(s), failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.GetServiceOrCreateInstance(s), failureStatus, tags, timeout)); } // NOTE: AddTypeActivatedCheck has overloads rather than default parameters values, because default parameter values don't @@ -187,5 +242,44 @@ namespace Microsoft.Extensions.DependencyInjection return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.CreateInstance(s, args), failureStatus, tags)); } + + /// + /// 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. + /// 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( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + 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, s => ActivatorUtilities.CreateInstance(s, args), failureStatus, tags, timeout)); + } } } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index d7dfdd90ae..ba27ab5554 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -22,11 +22,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable tags = null) + IEnumerable tags) + { + 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 . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -44,7 +64,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -55,11 +75,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable tags = null) + IEnumerable tags) + { + 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 . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -77,7 +117,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -88,11 +128,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, Func> check, - IEnumerable tags = null) + IEnumerable tags) + { + return AddAsyncCheck(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 . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -110,7 +170,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -121,11 +181,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, Func> check, - IEnumerable tags = null) + IEnumerable tags) + { + return AddAsyncCheck(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 . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -143,7 +223,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } } } diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index fa08cccd20..ba0a2f32d5 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -424,6 +424,32 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks }); } + [Fact] + public async Task CheckHealthAsync_TimeoutReturnsUnhealthy() + { + // Arrange + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("timeout", async (ct) => + { + await Task.Delay(2000, ct); + return HealthCheckResult.Healthy(); + }, timeout: TimeSpan.FromMilliseconds(100)); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries, + actual => + { + Assert.Equal("timeout", actual.Key); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + }); + } + private static DefaultHealthCheckService CreateHealthChecksService(Action configure) { var services = new ServiceCollection();