diff --git a/src/Particular.LicensingComponent.UnitTests/AuditQuery_Tests.cs b/src/Particular.LicensingComponent.UnitTests/AuditQuery_Tests.cs index 7bb790195e..b9318ecc88 100644 --- a/src/Particular.LicensingComponent.UnitTests/AuditQuery_Tests.cs +++ b/src/Particular.LicensingComponent.UnitTests/AuditQuery_Tests.cs @@ -206,7 +206,7 @@ public Task> GetEndpoints(CancellationToken cancellationToken) class AuditCountApi_ReturningThreeAuditCounts : IAuditCountApi { - public async Task> GetEndpointAuditCounts(string endpoint, CancellationToken token) + public async Task> GetEndpointAuditCounts(string endpoint, CancellationToken cancellationToken) { var auditCounts = new List { diff --git a/src/Particular.LicensingComponent.UnitTests/Infrastructure/FakeAuditCountApi.cs b/src/Particular.LicensingComponent.UnitTests/Infrastructure/FakeAuditCountApi.cs index b9ea54a89d..a130d66264 100644 --- a/src/Particular.LicensingComponent.UnitTests/Infrastructure/FakeAuditCountApi.cs +++ b/src/Particular.LicensingComponent.UnitTests/Infrastructure/FakeAuditCountApi.cs @@ -8,6 +8,6 @@ class FakeAuditCountApi : IAuditCountApi { - public Task> GetEndpointAuditCounts(string endpoint, CancellationToken token) => throw new NotImplementedException(); + public Task> GetEndpointAuditCounts(string endpoint, CancellationToken cancellationToken) => throw new NotImplementedException(); } } diff --git a/src/ServiceControl.Api/.editorconfig b/src/ServiceControl.Api/.editorconfig deleted file mode 100644 index 1942f21999..0000000000 --- a/src/ServiceControl.Api/.editorconfig +++ /dev/null @@ -1,7 +0,0 @@ -[*.cs] - -# 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.PS0017.severity = none diff --git a/src/ServiceControl.Api/IAuditCountApi.cs b/src/ServiceControl.Api/IAuditCountApi.cs index ca1975f3a0..bb8ef4bd07 100644 --- a/src/ServiceControl.Api/IAuditCountApi.cs +++ b/src/ServiceControl.Api/IAuditCountApi.cs @@ -7,6 +7,6 @@ public interface IAuditCountApi { - Task> GetEndpointAuditCounts(string endpoint, CancellationToken token); + Task> GetEndpointAuditCounts(string endpoint, CancellationToken cancellationToken = default); } } diff --git a/src/ServiceControl.Api/IConfigurationApi.cs b/src/ServiceControl.Api/IConfigurationApi.cs index 153380bd14..6a2dc1ad7c 100644 --- a/src/ServiceControl.Api/IConfigurationApi.cs +++ b/src/ServiceControl.Api/IConfigurationApi.cs @@ -6,10 +6,10 @@ public interface IConfigurationApi { - Task GetUrls(string baseUrl, CancellationToken cancellationToken); + Task GetUrls(string baseUrl, CancellationToken cancellationToken = default); - Task GetConfig(CancellationToken cancellationToken); + Task GetConfig(CancellationToken cancellationToken = default); - Task GetRemoteConfigs(CancellationToken cancellationToken); + Task GetRemoteConfigs(CancellationToken cancellationToken = default); } } diff --git a/src/ServiceControl.Api/IEndpointsApi.cs b/src/ServiceControl.Api/IEndpointsApi.cs index a2345ade4e..9d34a346ca 100644 --- a/src/ServiceControl.Api/IEndpointsApi.cs +++ b/src/ServiceControl.Api/IEndpointsApi.cs @@ -7,6 +7,6 @@ public interface IEndpointsApi { - Task> GetEndpoints(CancellationToken cancellationToken); + Task> GetEndpoints(CancellationToken cancellationToken = default); } } diff --git a/src/ServiceControl.DomainEvents/.editorconfig b/src/ServiceControl.DomainEvents/.editorconfig deleted file mode 100644 index dc8438ce16..0000000000 --- a/src/ServiceControl.DomainEvents/.editorconfig +++ /dev/null @@ -1,7 +0,0 @@ -[*.cs] - -# 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.PS0019.severity = none diff --git a/src/ServiceControl.DomainEvents/DomainEvents.cs b/src/ServiceControl.DomainEvents/DomainEvents.cs index 0367f199fb..4989590929 100644 --- a/src/ServiceControl.DomainEvents/DomainEvents.cs +++ b/src/ServiceControl.DomainEvents/DomainEvents.cs @@ -8,7 +8,7 @@ public class DomainEvents(IServiceProvider serviceProvider, ILogger logger) : IDomainEvents { - public async Task Raise(T domainEvent, CancellationToken cancellationToken) where T : IDomainEvent + public async Task Raise(T domainEvent, CancellationToken cancellationToken = default) where T : IDomainEvent { var handlers = serviceProvider.GetServices>(); foreach (var handler in handlers) @@ -18,6 +18,10 @@ public async Task Raise(T domainEvent, CancellationToken cancellationToken) w await handler.Handle(domainEvent, cancellationToken) .ConfigureAwait(false); } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } catch (Exception e) { logger.LogError(e, "Unexpected error publishing domain event {EventType}", typeof(T)); @@ -33,6 +37,10 @@ await handler.Handle(domainEvent, cancellationToken) await handler.Handle(domainEvent, cancellationToken) .ConfigureAwait(false); } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } catch (Exception e) { logger.LogError(e, "Unexpected error publishing domain event {EventType}", typeof(T)); diff --git a/src/ServiceControl.Hosting/.editorconfig b/src/ServiceControl.Hosting/.editorconfig deleted file mode 100644 index 2815635efb..0000000000 --- a/src/ServiceControl.Hosting/.editorconfig +++ /dev/null @@ -1,6 +0,0 @@ -[*.cs] - -# 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.PS0018.severity = none diff --git a/src/ServiceControl.Hosting/Auth/PermissionPolicyProvider.cs b/src/ServiceControl.Hosting/Auth/PermissionPolicyProvider.cs index 500e50c335..55b7caea56 100644 --- a/src/ServiceControl.Hosting/Auth/PermissionPolicyProvider.cs +++ b/src/ServiceControl.Hosting/Auth/PermissionPolicyProvider.cs @@ -52,6 +52,7 @@ static FrozenDictionary BuildPolicies(OpenIdConnect : AllowAll, StringComparer.Ordinal); +#pragma warning disable PS0018 // IAuthorizationPolicyProvider declares these without a CancellationToken public Task GetPolicyAsync(string policyName) => Task.FromResult(policies.GetValueOrDefault(policyName)); @@ -64,4 +65,5 @@ public Task GetDefaultPolicyAsync() public Task GetFallbackPolicyAsync() => Task.FromResult(authorizationOptions.Value.FallbackPolicy); +#pragma warning restore PS0018 } diff --git a/src/ServiceControl.Hosting/Auth/RolesClaimsTransformation.cs b/src/ServiceControl.Hosting/Auth/RolesClaimsTransformation.cs index a70f166769..2bb54dec50 100644 --- a/src/ServiceControl.Hosting/Auth/RolesClaimsTransformation.cs +++ b/src/ServiceControl.Hosting/Auth/RolesClaimsTransformation.cs @@ -27,6 +27,7 @@ public sealed class RolesClaimsTransformation(OpenIdConnectSettings oidcSettings // placeholder is required because a Claim value cannot be null. const string SentinelClaimValue = "1"; +#pragma warning disable PS0018 // IClaimsTransformation declares this without a CancellationToken public Task TransformAsync(ClaimsPrincipal principal) { var isAuthenticated = principal.Identity?.IsAuthenticated == true; @@ -49,6 +50,7 @@ public Task TransformAsync(ClaimsPrincipal principal) transformed.AddIdentity(new ClaimsIdentity(claims)); return Task.FromResult(transformed); } +#pragma warning restore PS0018 // True once this transformation has stamped its sentinel claim, keeping TransformAsync // idempotent across the repeated calls ASP.NET makes for the same principal. diff --git a/src/ServiceControl.Infrastructure.Metrics/.editorconfig b/src/ServiceControl.Infrastructure.Metrics/.editorconfig deleted file mode 100644 index 3e7fc9797e..0000000000 --- a/src/ServiceControl.Infrastructure.Metrics/.editorconfig +++ /dev/null @@ -1,7 +0,0 @@ -[*.cs] - -# 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.PS0018.severity = none -dotnet_diagnostic.PS0020.severity = none diff --git a/src/ServiceControl.Infrastructure.Metrics/MetricsReporter.cs b/src/ServiceControl.Infrastructure.Metrics/MetricsReporter.cs index 73dd600550..a7f271d5cd 100644 --- a/src/ServiceControl.Infrastructure.Metrics/MetricsReporter.cs +++ b/src/ServiceControl.Infrastructure.Metrics/MetricsReporter.cs @@ -32,7 +32,7 @@ public void Start() await Task.Delay(interval, tokenSource.Token).ConfigureAwait(false); } } - catch (OperationCanceledException) when (tokenSource.IsCancellationRequested) + catch (OperationCanceledException) when (tokenSource.Token.IsCancellationRequested) { //no-op } @@ -54,14 +54,14 @@ void Print() } } - public Task Stop() + public async Task Stop(CancellationToken cancellationToken = default) { if (tokenSource == null) { - return Task.CompletedTask; + return; } - tokenSource.Cancel(); - return task; + await tokenSource.CancelAsync().ConfigureAwait(false); + await task.WaitAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/ServiceControl.Infrastructure/.editorconfig b/src/ServiceControl.Infrastructure/.editorconfig index ac122a63b6..1ca766dd45 100644 --- a/src/ServiceControl.Infrastructure/.editorconfig +++ b/src/ServiceControl.Infrastructure/.editorconfig @@ -1,10 +1,6 @@ -[*.cs] - -# 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 +# Cancellation analyzer debt, narrowed to the one file still owed. MessageActionAuditLogExtensions +# takes a Func callback that has to become Func, which changes the +# lambda at a dozen controller call sites, so it moves with the controllers in Phase 2. +[Auth/MessageActionAuditLogExtensions.cs] dotnet_diagnostic.PS0013.severity = none dotnet_diagnostic.PS0018.severity = none -dotnet_diagnostic.PS0020.severity = none -dotnet_diagnostic.PS0021.severity = none diff --git a/src/ServiceControl.Infrastructure/AsyncTimer.cs b/src/ServiceControl.Infrastructure/AsyncTimer.cs index 045e8d229e..37a3026105 100644 --- a/src/ServiceControl.Infrastructure/AsyncTimer.cs +++ b/src/ServiceControl.Infrastructure/AsyncTimer.cs @@ -68,7 +68,7 @@ public TimerJob(Func> callback, }, CancellationToken.None); } - public async Task Stop(CancellationToken cancellationToken) + public async Task Stop(CancellationToken cancellationToken = default) { if (tokenSource == null) { @@ -87,9 +87,11 @@ public async Task Stop(CancellationToken cancellationToken) { await task.WaitAsync(cancellationToken).ConfigureAwait(false); } - catch (OperationCanceledException) when (tokenSource.IsCancellationRequested) + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { - //NOOP + // The job's own cancellation is already swallowed inside the loop, so the only way out + // here is the caller giving up on waiting. tokenSource is disposed by this point, which + // is why the filter must not reach through it for a token. } } diff --git a/src/ServiceControl.Infrastructure/IntegratedSetup.cs b/src/ServiceControl.Infrastructure/IntegratedSetup.cs index a72a565e7c..6da499821d 100644 --- a/src/ServiceControl.Infrastructure/IntegratedSetup.cs +++ b/src/ServiceControl.Infrastructure/IntegratedSetup.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; + using System.Threading; using System.Threading.Tasks; public static class IntegratedSetup @@ -11,7 +12,7 @@ public static class IntegratedSetup const string SetupAndRunCmd = "--setup-and-run"; const string SetupCmd = "--setup"; - public static async Task Run() + public static async Task Run(CancellationToken cancellationToken = default) { // Using GetCommandLineArgs instead of the args passed into Main because GetCommandLineArgs provides the entry assembly path var args = Environment.GetCommandLineArgs().ToList(); @@ -52,7 +53,7 @@ public static async Task Run() process.BeginOutputReadLine(); process.BeginErrorReadLine(); - await process.WaitForExitAsync().ConfigureAwait(false); + await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); return process.ExitCode; } diff --git a/src/ServiceControl.Infrastructure/ReadOnlyStream.cs b/src/ServiceControl.Infrastructure/ReadOnlyStream.cs index 6c5983bfae..3d52028f47 100644 --- a/src/ServiceControl.Infrastructure/ReadOnlyStream.cs +++ b/src/ServiceControl.Infrastructure/ReadOnlyStream.cs @@ -38,7 +38,7 @@ public override void CopyTo(Stream destination, int bufferSize) destination.Write(source); } - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { @@ -100,7 +100,7 @@ public override int Read(Span buffer) return bytesToCopy; } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { @@ -150,13 +150,13 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken public override void WriteByte(byte value) => throw new NotSupportedException(); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw new NotSupportedException(); + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public override void Write(ReadOnlySpan buffer) => throw new NotSupportedException(); - public override Task FlushAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); + public override Task FlushAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException(); public override void Flush() => throw new NotSupportedException(); diff --git a/src/ServiceControl.Infrastructure/Watchdog.cs b/src/ServiceControl.Infrastructure/Watchdog.cs index 443abe3b5d..cfdacb9e30 100644 --- a/src/ServiceControl.Infrastructure/Watchdog.cs +++ b/src/ServiceControl.Infrastructure/Watchdog.cs @@ -35,13 +35,14 @@ ILogger log this.log = log; } - public Task OnFailure(string failure) + public async Task OnFailure(string failure, CancellationToken cancellationToken = default) { reportFailure(failure); - return ensureStopped(shutdownTokenSource.Token); + using var stopTokenSource = CancellationTokenSource.CreateLinkedTokenSource(shutdownTokenSource.Token, cancellationToken); + await ensureStopped(stopTokenSource.Token).ConfigureAwait(false); } - public Task Start(Action onFailedOnStartup, CancellationToken cancellationToken) + public Task Start(Action onFailedOnStartup, CancellationToken cancellationToken = default) { watchdog = Task.Run(async () => { @@ -51,6 +52,10 @@ public Task Start(Action onFailedOnStartup, CancellationToken cancellationToken) while (!shutdownTokenSource.IsCancellationRequested) { + // Two tokens in play: the shutdown token, and a linked one that additionally trips after + // MaxStartDurationMs. Only shutdown means "stop watching"; the timeout is a startup failure + // and must fall through to the Exception handler so it is reported and retried. +#pragma warning disable PS0021 try { // Host builder start is launching the loop. The watch dog loop task runs in isolation @@ -65,7 +70,7 @@ public Task Start(Action onFailedOnStartup, CancellationToken cancellationToken) clearFailure(); startup = false; } - catch (OperationCanceledException e) when (shutdownTokenSource.IsCancellationRequested) + catch (OperationCanceledException e) when (shutdownTokenSource.Token.IsCancellationRequested) { log.LogDebug(e, "Cancelled"); return; @@ -83,11 +88,12 @@ public Task Start(Action onFailedOnStartup, CancellationToken cancellationToken) log.LogError(e, "Error while trying to start {TaskName}. Starting will be retried in {TimeToWaitBetweenStartupAttempts}", taskName, timeToWaitBetweenStartupAttempts); } +#pragma warning restore PS0021 try { await Task.Delay(timeToWaitBetweenStartupAttempts, shutdownTokenSource.Token).ConfigureAwait(false); } - catch (OperationCanceledException) when (shutdownTokenSource.IsCancellationRequested) + catch (OperationCanceledException) when (shutdownTokenSource.Token.IsCancellationRequested) { //Ignore, no need to log cancellation of delay } @@ -97,7 +103,7 @@ public Task Start(Action onFailedOnStartup, CancellationToken cancellationToken) return Task.CompletedTask; } - public async Task Stop(CancellationToken cancellationToken) + public async Task Stop(CancellationToken cancellationToken = default) { try { diff --git a/src/ServiceControl/Infrastructure/Api/AuditCountApi.cs b/src/ServiceControl/Infrastructure/Api/AuditCountApi.cs index 5bbe2d5e14..81e8311b02 100644 --- a/src/ServiceControl/Infrastructure/Api/AuditCountApi.cs +++ b/src/ServiceControl/Infrastructure/Api/AuditCountApi.cs @@ -10,7 +10,7 @@ class AuditCountApi(GetAuditCountsForEndpointApi auditCountsForEndpointApi) : IAuditCountApi { - public async Task> GetEndpointAuditCounts(string endpoint, CancellationToken token) => + public async Task> GetEndpointAuditCounts(string endpoint, CancellationToken cancellationToken) => (await auditCountsForEndpointApi.Execute(new AuditCountsForEndpointContext(new PagingInfo(), endpoint), $"/api/endpoints/{endpoint}/audit-count")).Results; } \ No newline at end of file diff --git a/src/ServiceControl/Infrastructure/Metrics/MetricsReporterHostedService.cs b/src/ServiceControl/Infrastructure/Metrics/MetricsReporterHostedService.cs index 5992b7c0fe..4fe465cde9 100644 --- a/src/ServiceControl/Infrastructure/Metrics/MetricsReporterHostedService.cs +++ b/src/ServiceControl/Infrastructure/Metrics/MetricsReporterHostedService.cs @@ -32,7 +32,7 @@ public async Task StopAsync(CancellationToken cancellationToken) { try { - await reporter.Stop(); + await reporter.Stop(cancellationToken); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) {