diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/.editorconfig b/src/ServiceControl.Monitoring.AcceptanceTests/.editorconfig index bbcb303765..5f68a610b3 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/.editorconfig +++ b/src/ServiceControl.Monitoring.AcceptanceTests/.editorconfig @@ -2,9 +2,3 @@ # Justification: Test project dotnet_diagnostic.CA2007.severity = none - -# Cancellation analyzer debt. These fire because Particular.Analyzers is no longer pinned to 0.9.0. -# They are scheduled work, not accepted exceptions: remove a line once this project has no -# violations of that rule left, and never add a rule back to this list. -dotnet_diagnostic.PS0003.severity = none -dotnet_diagnostic.PS0018.severity = none diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs b/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs index 59ca54e8bd..009dd08a0c 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs +++ b/src/ServiceControl.Monitoring.AcceptanceTests/PerformanceTests.cs @@ -64,9 +64,9 @@ public async Task GetMonitoredEndpointsQueryTest(int numberOfEndpoints, int numb var reporters = new[] { - BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, source, (e, i) => criticalTimeStore.Store(e, i, EndpointMessageType.Unknown(i.EndpointName))), - BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, source, (e, i) => processingTimeStore.Store(e, i, EndpointMessageType.Unknown(i.EndpointName))), - BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, source, (e, i) => retriesStore.Store(e, i, EndpointMessageType.Unknown(i.EndpointName))) + BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, (e, i) => criticalTimeStore.Store(e, i, EndpointMessageType.Unknown(i.EndpointName)), source.Token), + BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, (e, i) => processingTimeStore.Store(e, i, EndpointMessageType.Unknown(i.EndpointName)), source.Token), + BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, (e, i) => retriesStore.Store(e, i, EndpointMessageType.Unknown(i.EndpointName)), source.Token) }.SelectMany(i => i).ToArray(); var histogram = CreateTimeHistogram(); @@ -123,9 +123,9 @@ public async Task GetMonitoredSingleEndpointQueryTest(int numberOfInstances, int var reporters = new[] { - BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, source, (e, i) => criticalTimeStore.Store(e, i, getter())), - BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, source, (e, i) => processingTimeStore.Store(e, i, getter())), - BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, source, (e, i) => retriesStore.Store(e, i, getter())) + BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, (e, i) => criticalTimeStore.Store(e, i, getter()), source.Token), + BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, (e, i) => processingTimeStore.Store(e, i, getter()), source.Token), + BuildReporters(sendReportEvery, numberOfEntriesInReport, instances, (e, i) => retriesStore.Store(e, i, getter()), source.Token) }.SelectMany(i => i).ToArray(); var histogram = CreateTimeHistogram(); @@ -149,21 +149,21 @@ public async Task GetMonitoredSingleEndpointQueryTest(int numberOfInstances, int Report("Reporters", reportFinalHistogram, TimeSpan.FromMilliseconds(20)); } - static IEnumerable> BuildReporters(int sendReportEvery, int numberOfEntriesInReport, EndpointInstanceId[] instances, CancellationTokenSource source, Action store) + static IEnumerable> BuildReporters(int sendReportEvery, int numberOfEntriesInReport, EndpointInstanceId[] instances, Action store, CancellationToken cancellationToken) { return instances - .Select(instance => StartReporter(sendReportEvery, numberOfEntriesInReport, source, instance, store)) + .Select(instance => StartReporter(sendReportEvery, numberOfEntriesInReport, instance, store, cancellationToken)) .ToArray(); } - static Task StartReporter(int sendReportEvery, int numberOfEntriesInReport, CancellationTokenSource source, EndpointInstanceId instance, Action store) + static Task StartReporter(int sendReportEvery, int numberOfEntriesInReport, EndpointInstanceId instance, Action store, CancellationToken cancellationToken) { return Task.Run(async () => { var entries = new RawMessage.Entry[numberOfEntriesInReport]; var histogram = CreateTimeHistogram(); - while (source.IsCancellationRequested == false) + while (cancellationToken.IsCancellationRequested == false) { var now = DateTime.UtcNow; @@ -178,7 +178,9 @@ static Task StartReporter(int sendReportEvery, int numberOfEntrie var elapsed = Stopwatch.GetTimestamp() - start; histogram.RecordValue(elapsed); - await Task.Delay(sendReportEvery); + // Not cancelled: the loop exits on the next check and the task has to complete + // normally, because MergeHistograms reads the histogram it returns. + await Task.Delay(sendReportEvery, CancellationToken.None); } return histogram; @@ -247,8 +249,8 @@ public void TrackEndpointInputQueue(EndpointToQueueMapping queueToTrack) { } - public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task StartAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task StopAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentBehavior.cs b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentBehavior.cs index 167d222f05..b8e0c97315 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentBehavior.cs +++ b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentBehavior.cs @@ -21,7 +21,9 @@ public ServiceControlComponentBehavior(ITransportIntegration transportToUse, Act public HttpClient HttpClient => runner.HttpClient; public JsonSerializerOptions SerializerOptions => runner.SerializerOptions; +#pragma warning disable PS0018 // IComponentBehavior declares this without a CancellationToken public async Task CreateRunner(RunDescriptor run) +#pragma warning restore PS0018 { runner = new ServiceControlComponentRunner(transportIntegration, setSettings, customConfiguration); await runner.Initialize(run); diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index a5d7623d79..545f2896e5 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -34,9 +34,9 @@ class ServiceControlComponentRunner( public HttpClient HttpClient { get; private set; } public JsonSerializerOptions SerializerOptions => Infrastructure.SerializerOptions.Default; - public Task Initialize(RunDescriptor run) => InitializeServiceControl(run.ScenarioContext); + public Task Initialize(RunDescriptor run, CancellationToken cancellationToken = default) => InitializeServiceControl(run.ScenarioContext, cancellationToken); - async Task InitializeServiceControl(ScenarioContext context) + async Task InitializeServiceControl(ScenarioContext context, CancellationToken cancellationToken) { LoggerUtil.ActiveLoggers = Loggers.Test; settings = new Settings(transportType: transportToUse.TypeName) @@ -79,7 +79,7 @@ async Task InitializeServiceControl(ScenarioContext context) using (new DiagnosticTimer($"Creating infrastructure for {settings.InstanceName}")) { var setupCommand = new SetupCommand(); - await setupCommand.Execute(new HostArguments([]), settings); + await setupCommand.Execute(new HostArguments([]), settings, cancellationToken); } var configuration = new EndpointConfiguration(settings.InstanceName); @@ -127,7 +127,7 @@ async Task InitializeServiceControl(ScenarioContext context) host.UseTestRemoteIp(); host.UseServiceControlAuthentication(settings.OpenIdConnectSettings.Enabled); host.UseServiceControlMonitoring(settings.ForwardedHeadersSettings, settings.HttpsSettings, settings.CorsSettings); - await host.StartAsync(); + await host.StartAsync(cancellationToken); HttpClient = host.Services.GetRequiredKeyedService(settings.InstanceName).CreateClient(); } diff --git a/src/ServiceControl.Monitoring/.editorconfig b/src/ServiceControl.Monitoring/.editorconfig index c1cb416fea..aff82c0034 100644 --- a/src/ServiceControl.Monitoring/.editorconfig +++ b/src/ServiceControl.Monitoring/.editorconfig @@ -2,10 +2,3 @@ # Justification: Application synchronization contexts don't require ConfigureAwait(false) dotnet_diagnostic.CA2007.severity = none - -# Cancellation analyzer debt. These fire because Particular.Analyzers is no longer pinned to 0.9.0. -# They are scheduled work, not accepted exceptions: remove a line once this project has no -# violations of that rule left, and never add a rule back to this list. -dotnet_diagnostic.PS0003.severity = none -dotnet_diagnostic.PS0018.severity = none -dotnet_diagnostic.PS0019.severity = none diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/AbstractCommand.cs b/src/ServiceControl.Monitoring/Hosting/Commands/AbstractCommand.cs index 9dc057200f..087fdef3be 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/AbstractCommand.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/AbstractCommand.cs @@ -1,9 +1,10 @@ namespace ServiceControl.Monitoring { + using System.Threading; using System.Threading.Tasks; abstract class AbstractCommand { - public abstract Task Execute(HostArguments args, Settings settings); + public abstract Task Execute(HostArguments args, Settings settings, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs b/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs index cf5a79c3d4..31fb3b9049 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/CommandRunner.cs @@ -1,14 +1,15 @@ namespace ServiceControl.Monitoring { using System; + using System.Threading; using System.Threading.Tasks; class CommandRunner(Type commandType) { - public async Task Execute(HostArguments args, Settings settings) + public async Task Execute(HostArguments args, Settings settings, CancellationToken cancellationToken = default) { var command = (AbstractCommand)Activator.CreateInstance(commandType); - await command.Execute(args, settings); + await command.Execute(args, settings, cancellationToken); } } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/RunCommand.cs b/src/ServiceControl.Monitoring/Hosting/Commands/RunCommand.cs index 6e197e463a..8a60faf6ff 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/RunCommand.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/RunCommand.cs @@ -1,5 +1,6 @@ namespace ServiceControl.Monitoring { + using System.Threading; using System.Threading.Tasks; using Infrastructure; using Infrastructure.WebApi; @@ -10,7 +11,7 @@ namespace ServiceControl.Monitoring class RunCommand : AbstractCommand { - public override async Task Execute(HostArguments args, Settings settings) + public override async Task Execute(HostArguments args, Settings settings, CancellationToken cancellationToken = default) { var endpointConfiguration = new EndpointConfiguration(settings.InstanceName); diff --git a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs index 22a84b27f7..252419e37b 100644 --- a/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs +++ b/src/ServiceControl.Monitoring/Hosting/Commands/SetupCommand.cs @@ -1,5 +1,6 @@ namespace ServiceControl.Monitoring { + using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using ServiceControl.Infrastructure; @@ -7,7 +8,7 @@ namespace ServiceControl.Monitoring class SetupCommand : AbstractCommand { - public override Task Execute(HostArguments args, Settings settings) + public override Task Execute(HostArguments args, Settings settings, CancellationToken cancellationToken = default) { if (args.SkipQueueCreation) { @@ -18,7 +19,7 @@ public override Task Execute(HostArguments args, Settings settings) var transportSettings = settings.ToTransportSettings(); transportSettings.ErrorQueue = settings.ErrorQueue; var transportCustomization = TransportFactory.Create(transportSettings); - return transportCustomization.ProvisionQueues(transportSettings, []); + return transportCustomization.ProvisionQueues(transportSettings, [], cancellationToken); } } } \ No newline at end of file diff --git a/src/ServiceControl.Monitoring/Infrastructure/RemoveExpiredEndpointInstances.cs b/src/ServiceControl.Monitoring/Infrastructure/RemoveExpiredEndpointInstances.cs index c563aa45b0..4a7228a856 100644 --- a/src/ServiceControl.Monitoring/Infrastructure/RemoveExpiredEndpointInstances.cs +++ b/src/ServiceControl.Monitoring/Infrastructure/RemoveExpiredEndpointInstances.cs @@ -17,7 +17,7 @@ public class RemoveExpiredEndpointInstances( { const int IntervalInMinutes = 5; - protected override async Task ExecuteAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken cancellationToken = default) { logger.LogInformation($"Starting {nameof(RemoveExpiredEndpointInstances)}"); diff --git a/src/ServiceControl.Monitoring/Infrastructure/ReportThroughputHostedService.cs b/src/ServiceControl.Monitoring/Infrastructure/ReportThroughputHostedService.cs index 5789593d4e..d2af476bea 100644 --- a/src/ServiceControl.Monitoring/Infrastructure/ReportThroughputHostedService.cs +++ b/src/ServiceControl.Monitoring/Infrastructure/ReportThroughputHostedService.cs @@ -13,7 +13,7 @@ class ReportThroughputHostedService(ILogger logger, IMessageSession session, IEndpointMetricsApi endpointMetricsApi, Settings settings, TimeProvider timeProvider, ITransportCustomization transportCustomization) : BackgroundService { - protected override async Task ExecuteAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken cancellationToken = default) { logger.LogInformation($"Starting {nameof(ReportThroughputHostedService)}"); @@ -29,7 +29,10 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) { await ReportOnThroughput(serviceControlThroughputDataQueue, cancellationToken); } +#pragma warning disable PS0019 // The filter already excludes OperationCanceledException, so cancellation + // is left to the outer handler. catch (Exception ex) when (ex is not OperationCanceledException) +#pragma warning restore PS0019 { if (ex.InnerException is not null and QueueNotFoundException) { diff --git a/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs b/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs index 3bc9d0ec21..cad60e9ce5 100644 --- a/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs +++ b/src/ServiceControl.Monitoring/Licensing/LicenseCheckHostedService.cs @@ -9,7 +9,7 @@ class LicenseCheckHostedService(ActiveLicense activeLicense, IAsyncTimer scheduler, ILogger logger) : IHostedService { - public Task StartAsync(CancellationToken cancellationToken) + public Task StartAsync(CancellationToken cancellationToken = default) { var due = TimeSpan.FromHours(8); timer = scheduler.Schedule(_ => @@ -20,7 +20,7 @@ public Task StartAsync(CancellationToken cancellationToken) return Task.CompletedTask; } - public Task StopAsync(CancellationToken cancellationToken) => timer.Stop(cancellationToken); + public Task StopAsync(CancellationToken cancellationToken = default) => timer.Stop(cancellationToken); TimerJob timer;