Skip to content

Commit 829f13a

Browse files
committed
Updated healthcheck lib
1 parent 1df0019 commit 829f13a

31 files changed

Lines changed: 670 additions & 430 deletions

eShopOnContainers-ServicesAndWebApps.sln

Lines changed: 103 additions & 103 deletions
Large diffs are not rendered by default.

src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckMiddleware.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.Threading;
46
using System.Threading.Tasks;
57
using Microsoft.AspNetCore.Http;
68
using Microsoft.AspNetCore.Http.Features;
@@ -11,30 +13,34 @@ namespace Microsoft.AspNetCore.HealthChecks
1113
{
1214
public class HealthCheckMiddleware
1315
{
14-
private RequestDelegate _next;
15-
private string _path;
16-
private int? _port;
17-
private IHealthCheckService _service;
16+
private readonly RequestDelegate _next;
17+
private readonly string _path;
18+
private readonly int? _port;
19+
private readonly IHealthCheckService _service;
20+
private readonly TimeSpan _timeout;
1821

19-
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port)
22+
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, int port, TimeSpan timeout)
2023
{
2124
_port = port;
2225
_service = service;
2326
_next = next;
27+
_timeout = timeout;
2428
}
2529

26-
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path)
30+
public HealthCheckMiddleware(RequestDelegate next, IHealthCheckService service, string path, TimeSpan timeout)
2731
{
2832
_path = path;
2933
_service = service;
3034
_next = next;
35+
_timeout = timeout;
3136
}
3237

3338
public async Task Invoke(HttpContext context)
3439
{
3540
if (IsHealthCheckRequest(context))
3641
{
37-
var result = await _service.CheckHealthAsync();
42+
var timeoutTokenSource = new CancellationTokenSource(_timeout);
43+
var result = await _service.CheckHealthAsync(timeoutTokenSource.Token);
3844
var status = result.CheckStatus;
3945

4046
if (status != CheckStatus.Healthy)
@@ -60,7 +66,9 @@ private bool IsHealthCheckRequest(HttpContext context)
6066
}
6167

6268
if (context.Request.Path == _path)
69+
{
6370
return true;
71+
}
6472

6573
return false;
6674
}

src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckStartupFilter.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,32 @@ public class HealthCheckStartupFilter : IStartupFilter
1111
{
1212
private string _path;
1313
private int? _port;
14+
private TimeSpan _timeout;
1415

15-
public HealthCheckStartupFilter(int port)
16+
public HealthCheckStartupFilter(int port, TimeSpan timeout)
1617
{
1718
_port = port;
19+
_timeout = timeout;
1820
}
1921

20-
public HealthCheckStartupFilter(string path)
22+
public HealthCheckStartupFilter(string path, TimeSpan timeout)
2123
{
2224
_path = path;
25+
_timeout = timeout;
2326
}
2427

2528
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
2629
{
2730
return app =>
2831
{
2932
if (_port.HasValue)
30-
app.UseMiddleware<HealthCheckMiddleware>(_port);
33+
{
34+
app.UseMiddleware<HealthCheckMiddleware>(_port, _timeout);
35+
}
3136
else
32-
app.UseMiddleware<HealthCheckMiddleware>(_path);
37+
{
38+
app.UseMiddleware<HealthCheckMiddleware>(_path, _timeout);
39+
}
3340

3441
next(app);
3542
};

src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks/HealthCheckWebHostBuilderExtension.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,46 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using Microsoft.AspNetCore.HealthChecks;
56
using Microsoft.Extensions.DependencyInjection;
67

78
namespace Microsoft.AspNetCore.Hosting
89
{
910
public static class HealthCheckWebHostBuilderExtension
1011
{
12+
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
13+
1114
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port)
15+
=> UseHealthChecks(builder, port, DefaultTimeout);
16+
17+
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, int port, TimeSpan timeout)
1218
{
13-
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535");
19+
Guard.ArgumentValid(port > 0 && port < 65536, nameof(port), "Port must be a value between 1 and 65535.");
20+
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
1421

1522
builder.ConfigureServices(services =>
1623
{
1724
var existingUrl = builder.GetSetting(WebHostDefaults.ServerUrlsKey);
1825
builder.UseSetting(WebHostDefaults.ServerUrlsKey, $"{existingUrl};http://localhost:{port}");
1926

20-
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port));
27+
services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(port, timeout));
2128
});
2229
return builder;
2330
}
2431

2532
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path)
33+
=> UseHealthChecks(builder, path, DefaultTimeout);
34+
35+
public static IWebHostBuilder UseHealthChecks(this IWebHostBuilder builder, string path, TimeSpan timeout)
2636
{
2737
Guard.ArgumentNotNull(nameof(path), path);
2838
// REVIEW: Is there a better URL path validator somewhere?
29-
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values");
30-
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with /");
39+
Guard.ArgumentValid(!path.Contains("?"), nameof(path), "Path cannot contain query string values.");
40+
Guard.ArgumentValid(path.StartsWith("/"), nameof(path), "Path should start with '/'.");
41+
Guard.ArgumentValid(timeout > TimeSpan.Zero, nameof(timeout), "Health check timeout must be a positive time span.");
3142

32-
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path)));
43+
builder.ConfigureServices(services => services.AddSingleton<IStartupFilter>(new HealthCheckStartupFilter(path, timeout)));
3344
return builder;
3445
}
3546
}

src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/HealthCheckBuilderDataExtensions.cs renamed to src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/HealthCheckBuilderSqlServerExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace Microsoft.Extensions.HealthChecks
99
{
10-
public static class HealthCheckBuilderDataExtensions
10+
public static class HealthCheckBuilderSqlServerExtensions
1111
{
1212
public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, string name, string connectionString)
1313
{
@@ -33,7 +33,7 @@ public static HealthCheckBuilder AddSqlCheck(this HealthCheckBuilder builder, st
3333
}
3434
}
3535
}
36-
catch(Exception ex)
36+
catch (Exception ex)
3737
{
3838
return HealthCheckResult.Unhealthy($"SqlCheck({name}): Exception during check: {ex.GetType().FullName}");
3939
}

src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.Data/Microsoft.Extensions.HealthChecks.Data.csproj renamed to src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks.SqlServer/Microsoft.Extensions.HealthChecks.SqlServer.csproj

File renamed without changes.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Reflection;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.Extensions.DependencyInjection;
9+
10+
namespace Microsoft.Extensions.HealthChecks
11+
{
12+
public abstract class CachedHealthCheck
13+
{
14+
private static readonly TypeInfo HealthCheckTypeInfo = typeof(IHealthCheck).GetTypeInfo();
15+
16+
private volatile int _writerCount;
17+
18+
public CachedHealthCheck(string name, TimeSpan cacheDuration)
19+
{
20+
Guard.ArgumentNotNullOrEmpty(nameof(name), name);
21+
Guard.ArgumentValid(cacheDuration.TotalMilliseconds >= 0, nameof(cacheDuration), "Cache duration must be zero (disabled) or greater than zero.");
22+
23+
Name = name;
24+
CacheDuration = cacheDuration;
25+
}
26+
27+
public IHealthCheckResult CachedResult { get; internal set; }
28+
29+
public TimeSpan CacheDuration { get; }
30+
31+
public DateTimeOffset CacheExpiration { get; internal set; }
32+
33+
public string Name { get; }
34+
35+
protected virtual DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
36+
37+
protected abstract IHealthCheck Resolve(IServiceProvider serviceProvider);
38+
39+
public async ValueTask<IHealthCheckResult> RunAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default(CancellationToken))
40+
{
41+
while (CacheExpiration <= UtcNow)
42+
{
43+
// Can't use a standard lock here because of async, so we'll use this flag to determine when we should write a value,
44+
// and the waiters who aren't allowed to write will just spin wait for the new value.
45+
if (Interlocked.Exchange(ref _writerCount, 1) != 0)
46+
{
47+
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
48+
continue;
49+
}
50+
51+
try
52+
{
53+
var check = Resolve(serviceProvider);
54+
CachedResult = await check.CheckAsync(cancellationToken);
55+
}
56+
catch (OperationCanceledException)
57+
{
58+
CachedResult = HealthCheckResult.Unhealthy("The health check operation timed out");
59+
}
60+
catch (Exception ex)
61+
{
62+
CachedResult = HealthCheckResult.Unhealthy($"Exception during check: {ex.GetType().FullName}");
63+
}
64+
65+
CacheExpiration = UtcNow + CacheDuration;
66+
_writerCount = 0;
67+
break;
68+
}
69+
70+
return CachedResult;
71+
}
72+
73+
public static CachedHealthCheck FromHealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck)
74+
{
75+
Guard.ArgumentNotNull(nameof(healthCheck), healthCheck);
76+
77+
return new TypeOrHealthCheck_HealthCheck(name, cacheDuration, healthCheck);
78+
}
79+
80+
public static CachedHealthCheck FromType(string name, TimeSpan cacheDuration, Type healthCheckType)
81+
{
82+
Guard.ArgumentNotNull(nameof(healthCheckType), healthCheckType);
83+
Guard.ArgumentValid(HealthCheckTypeInfo.IsAssignableFrom(healthCheckType.GetTypeInfo()), nameof(healthCheckType), $"Health check must implement '{typeof(IHealthCheck).FullName}'.");
84+
85+
return new TypeOrHealthCheck_Type(name, cacheDuration, healthCheckType);
86+
}
87+
88+
class TypeOrHealthCheck_HealthCheck : CachedHealthCheck
89+
{
90+
private readonly IHealthCheck _healthCheck;
91+
92+
public TypeOrHealthCheck_HealthCheck(string name, TimeSpan cacheDuration, IHealthCheck healthCheck) : base(name, cacheDuration)
93+
=> _healthCheck = healthCheck;
94+
95+
protected override IHealthCheck Resolve(IServiceProvider serviceProvider) => _healthCheck;
96+
}
97+
98+
class TypeOrHealthCheck_Type : CachedHealthCheck
99+
{
100+
private readonly Type _healthCheckType;
101+
102+
public TypeOrHealthCheck_Type(string name, TimeSpan cacheDuration, Type healthCheckType) : base(name, cacheDuration)
103+
=> _healthCheckType = healthCheckType;
104+
105+
protected override IHealthCheck Resolve(IServiceProvider serviceProvider)
106+
=> (IHealthCheck)serviceProvider.GetRequiredService(_healthCheckType);
107+
}
108+
}
109+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Microsoft.Extensions.HealthChecks
9+
{
10+
public static class CachedHealthCheckExtensions
11+
{
12+
public static ValueTask<IHealthCheckResult> RunAsync(this CachedHealthCheck check, IServiceProvider serviceProvider)
13+
{
14+
Guard.ArgumentNotNull(nameof(check), check);
15+
16+
return check.RunAsync(serviceProvider, CancellationToken.None);
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)