Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
0c13fed
Introduce HealthCheckRegistration periodicity
francedot Jul 10, 2022
f9e52ee
Add missing public APIs contracts
francedot Jul 10, 2022
ac495ee
Revert unshipped API
francedot Jul 11, 2022
51ed0b8
Remove multiple blank lines
francedot Jul 11, 2022
6796dc1
Add missing AddTypeActivatedCheck param
francedot Jul 11, 2022
e6ba33c
Fix period argument null check
francedot Jul 11, 2022
6b692fa
Fix elapsed HC condition with timespan remainder
francedot Jul 13, 2022
a1cae01
Update HealthChecksBuilderDelegateExtensions api contract with period…
francedot Jul 13, 2022
a94160b
Update split publish from report generation
francedot Jul 13, 2022
3d79992
Revert HealthCheckPublisherHostedService
francedot Jul 14, 2022
fc43049
Introduce periodicity and report aggregation in DefaultHealthCheckSer…
francedot Jul 14, 2022
d72b113
Update unit test with required HealthCheckPublisherOptions
francedot Jul 14, 2022
efbd13f
Fix cancellation and add predicate for individual HCs
francedot Jul 15, 2022
f827296
Add unit tests for individual periodicity HCs
francedot Jul 15, 2022
f686266
Add groupby comment and simplify ToDictionary
francedot Jul 16, 2022
a91cce3
Update Entries Dictionary to KV<string,HealthReportEntry> to allow du…
francedot Jul 17, 2022
0fb75e2
Update unit tests for Entries KV list, add delay for collecting entries
francedot Jul 17, 2022
12c640a
Revert HealthReport to IReadOnlyDictionary cctor
francedot Jul 20, 2022
36754ec
Introduce HealthReportEntryDictionary with dup keys
francedot Jul 20, 2022
830f204
Add cancellation for individual periodicity HCs, introduce HealthRepo…
francedot Jul 20, 2022
80f1cd6
Add TaskCompletionSource for individual periodicity HCs
francedot Jul 20, 2022
e9b5ef7
Add HealthReportEntryDictionary public API
francedot Jul 20, 2022
bcea021
Revert HealthCheckPublisherHostedServiceTest
francedot Jul 20, 2022
90fcd4e
Add HealthReportEntryDictionary comments, fix string comparison
francedot Jul 21, 2022
a1274c1
Add HealthReportEntryDictionary unit tests
francedot Jul 21, 2022
29568a2
Add usage comments for Cancellation, predicate
francedot Jul 21, 2022
1836caf
Use compound assignment
francedot Jul 21, 2022
e47a61e
Revert HealthChecks/src/PublicAPI.Shipped.txt
francedot Jul 21, 2022
35d1500
Add HealthReportEntryDictionary public API
francedot Jul 21, 2022
6613e96
Update missing APIs HealthChecks/src/PublicAPI.Unshipped.txt
francedot Jul 21, 2022
ba51c87
Fix unused param periodHealthChecksMap
francedot Jul 21, 2022
2ff4e13
Fix unused param cancellationToken
francedot Jul 21, 2022
6b0ae5b
Fix cannot convert null literal to non-nullable reference type
francedot Jul 21, 2022
6aea5b5
Add nullable StringComparer
francedot Jul 21, 2022
0b252d9
Remove unneeded HealthReport API changes
francedot Jul 21, 2022
b2eacba
Refactor for new AddPeriodicCheck API
francedot Jul 21, 2022
6e49634
Remove unneeded AddHealthChecks API shipped change
francedot Jul 21, 2022
f572b9c
Revert remove unneeded AddHealthChecks API shipped change
francedot Jul 21, 2022
52422ff
Mark HealthReportEntryDictionary as internal
francedot Jul 24, 2022
4993cda
Remove unneeded fields, cancellation for non-default HCs
francedot Jul 24, 2022
80f3f48
Introduce delay per HC, set default values for period and delay
francedot Jul 25, 2022
a152a72
Aggregate timers per delay and period, override default values
francedot Jul 25, 2022
6d4b65d
Update API contract for individual HC
francedot Jul 25, 2022
b2b3960
Update unit tests for individual HCs, increase Task.Delay to include …
francedot Jul 25, 2022
a984239
Remove duplicate HealthReportEntryDictionary
francedot Jul 25, 2022
b8cf31d
Update comment to include delay aggregation
francedot Jul 26, 2022
639f9bc
Fix timers aggregation per delay, period
francedot Jul 26, 2022
be22fcc
Fix apply predicate at each HealthCheck run
francedot Jul 26, 2022
3e7cb5a
Introduce HealthCheckOptions
francedot Aug 3, 2022
64dbf5b
Refactor API to solve binary breaking
francedot Aug 3, 2022
23824b9
Suppress RS0027
francedot Aug 3, 2022
0c464ca
Migrate Public APIs to ValueTask
francedot Aug 4, 2022
9a076c6
Revert DefaultHealthCheckService impl
francedot Aug 4, 2022
578398c
Update HealthCheckPublisherHostedService to run individual HCs with c…
francedot Aug 4, 2022
e336956
Add Timeout as a per-HC property
francedot Aug 4, 2022
19d2804
Remove no longer used HealthReportEntryDictionary
francedot Aug 4, 2022
919f3bc
Rename HealthCheckRegistrationParameters
francedot Oct 19, 2022
75bbaa6
Align PublisherHostedService with OMEX implementation
francedot Oct 19, 2022
09af844
Add remaining tests for multiple periods and selectability
francedot Oct 19, 2022
449a16f
Simplify GroupBy lambda
francedot Oct 19, 2022
d14ee10
Update reusing PublisherOptions in AddCheck
francedot Jan 6, 2023
d3bde27
Move HealthCheckRegistration, HealthCheckContext, IHealthCheck API de…
francedot Jan 6, 2023
53f9a72
Align API contract for HealthCheckPublisherOptions
francedot Jan 6, 2023
d5fe2ba
Restore Task APIs
francedot Jan 8, 2023
61376e1
Move HC Report, HC Publisher to HealthChecks/src/
francedot Jan 8, 2023
f2585df
Align API contracts after move
francedot Jan 8, 2023
d2ab24e
Restore 449a16f
francedot Jan 15, 2023
c819d74
Refactor to HC Builder Add Registration
francedot Jan 15, 2023
54cd6b9
Align tests
francedot Jan 15, 2023
18cb9d6
Remove aggregate by timeout
francedot Jan 15, 2023
25899b0
Remove IsEnabled references
francedot Jan 15, 2023
4f3066c
Merge branch 'main' into healthcheck-periodicity
francedot Jan 15, 2023
9eb7aae
Update IHealthCheck.cs
francedot Jan 15, 2023
78fb052
Remove delay and period ctor
francedot Feb 2, 2023
23794cf
Update API with setters
francedot Feb 2, 2023
f581089
Restore ValidateRegistrations in DefaultHealthCheckService
francedot Mar 26, 2023
5dc3adf
Refactor for perf
francedot Mar 26, 2023
cdc01a3
Address CI failed checks
francedot Mar 26, 2023
21785ec
Restore DefaultHealthCheckService
francedot Mar 26, 2023
70c2f06
Remove SYSLIB1025
francedot Mar 26, 2023
664ad88
Restore HealthCheckServiceOptions
francedot Mar 26, 2023
e18b5fe
Simplify dict registrations to hashset
francedot Mar 27, 2023
cffab35
Merge branch 'healthcheck-periodicity' of https://github.com/francedo…
francedot Mar 27, 2023
ba7089d
Rename to group
francedot Mar 27, 2023
d602199
Remove timerOptions default
francedot Mar 28, 2023
9e67bee
Fix indentation
francedot Mar 28, 2023
273371b
Fix indentation
francedot Mar 28, 2023
f5a3f55
Fix indentation
francedot Mar 28, 2023
a8385ec
Update delay, periods by factor 10
francedot Mar 28, 2023
1fda48d
Update publishers to single object
francedot Mar 28, 2023
e7f9e04
Rsstore test periods
francedot Mar 28, 2023
17a8e6b
Update delay, period for blocking task
francedot Mar 28, 2023
0e8dd9d
Remove extra blank line.
mitchdenny Mar 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ public TimeSpan Timeout
}
}

/// <summary>
/// Gets or sets the individual delay applied to the health check after the application starts before executing
/// <see cref="IHealthCheckPublisher"/> instances. The delay is applied once at startup, and does
/// not apply to subsequent iterations.
/// </summary>
public TimeSpan? Delay { get; set; }

/// <summary>
/// Gets or sets the individual period used for the check.
/// </summary>
public TimeSpan? Period { get; set; }

/// <summary>
/// Gets or sets the health check name.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions src/HealthChecks/Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
#nullable enable
Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration.Delay.get -> System.TimeSpan?
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
Original file line number Diff line number Diff line change
Expand Up @@ -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.AspNetCore.Shared;
Expand All @@ -17,36 +19,45 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks;
internal sealed partial class HealthCheckPublisherHostedService : IHostedService
{
private readonly HealthCheckService _healthCheckService;
private readonly IOptions<HealthCheckPublisherOptions> _options;
private readonly IOptions<HealthCheckServiceOptions> _healthCheckServiceOptions;
private readonly IOptions<HealthCheckPublisherOptions> _healthCheckPublisherOptions;
private readonly ILogger _logger;
private readonly IHealthCheckPublisher[] _publishers;
private List<Timer>? _timers;

private readonly CancellationTokenSource _stopping;
private Timer? _timer;
private CancellationTokenSource? _runTokenSource;

public HealthCheckPublisherHostedService(
HealthCheckService healthCheckService,
IOptions<HealthCheckPublisherOptions> options,
IOptions<HealthCheckServiceOptions> healthCheckServiceOptions,
IOptions<HealthCheckPublisherOptions> healthCheckPublisherOptions,
ILogger<HealthCheckPublisherHostedService> logger,
IEnumerable<IHealthCheckPublisher> publishers)
{
ArgumentNullThrowHelper.ThrowIfNull(healthCheckService);
ArgumentNullThrowHelper.ThrowIfNull(options);
ArgumentNullThrowHelper.ThrowIfNull(healthCheckServiceOptions);
ArgumentNullThrowHelper.ThrowIfNull(healthCheckPublisherOptions);
ArgumentNullThrowHelper.ThrowIfNull(logger);
ArgumentNullThrowHelper.ThrowIfNull(publishers);

_healthCheckService = healthCheckService;
_options = options;
_healthCheckServiceOptions = healthCheckServiceOptions;
_healthCheckPublisherOptions = healthCheckPublisherOptions;
_logger = logger;
_publishers = publishers.ToArray();

_stopping = new CancellationTokenSource();
}

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 => _timer != null;
internal bool IsTimerRunning => _timers != null;

public Task StartAsync(CancellationToken cancellationToken = default)
{
Expand All @@ -55,9 +66,9 @@ 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.
_timer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _options.Value.Delay, period: _options.Value.Period);
_timers = CreateTimers();

return Task.CompletedTask;
}
Expand All @@ -78,16 +89,49 @@ public Task StopAsync(CancellationToken cancellationToken = default)
return Task.CompletedTask;
}

_timer?.Dispose();
_timer = null;
if (_timers != null)
{
foreach (var timer in _timers)
{
timer.Dispose();
}

_timers = 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 List<Timer> CreateTimers()
{
await RunAsync().ConfigureAwait(false);
var delayPeriodGroups = new HashSet<(TimeSpan Delay, TimeSpan Period)>();
foreach (var hc in _healthCheckServiceOptions.Value.Registrations)
{
var timerOptions = GetTimerOptions(hc);
delayPeriodGroups.Add(timerOptions);
}

var timers = new List<Timer>(delayPeriodGroups.Count);
foreach (var group in delayPeriodGroups)
{
var timer = CreateTimer(group);
timers.Add(timer);
}

return timers;
}

private Timer CreateTimer((TimeSpan Delay, TimeSpan Period) timerOptions)
{
return
NonCapturingTimer.Create(
async (state) =>
{
await RunAsync(timerOptions).ConfigureAwait(false);
},
null,
dueTime: timerOptions.Delay,
period: timerOptions.Period);
}

// Internal for testing
Expand All @@ -97,21 +141,21 @@ internal void CancelToken()
}

// Internal for testing
internal async Task RunAsync()
internal async Task RunAsync((TimeSpan Delay, TimeSpan Period) timerOptions)
{
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(timerOptions, cancellation.Token).ConfigureAwait(false);

Logger.HealthCheckPublisherProcessingEnd(_logger, duration.GetElapsedTime());
}
Expand All @@ -131,13 +175,21 @@ internal async Task RunAsync()
}
}

private async Task RunAsyncCore(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();

// 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,
// 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.
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];
Expand Down
Loading