From 5945faff04c32004663bc8c0ad9a4040b97c63eb Mon Sep 17 00:00:00 2001 From: John Simons Date: Wed, 12 Aug 2026 17:12:23 +1000 Subject: [PATCH] Propagate cancellation tokens through the installer engine and the Config app Retires the cancellation analyzer debt blocks in ServiceControlInstaller.Engine, ServiceControl.Management.PowerShell and ServiceControl.Config.Tests, and narrows the ServiceControl.Config block to the one boundary that cannot carry a token. Phase 7 of the propagation work. The engine change is what forces these projects to move together. AbstractCommandChecks' eight abstract members take tokens, the prompt callback becomes Func> throughout PathsValidator, the installable bases and the unattended installers, and ValidateNewInstance loses params so the token can be last. PowerShell's PowerShellCommandChecks and the three New-*Instance cmdlets follow, along with the Config app's ScmuCommandChecks, InstallerModule and add attachments. Two token drops are fixed in the Config app's Caliburn layer. RxScreen received a CancellationToken in ActivateAsync and DeactivateAsync and discarded it instead of passing it to OnInitialize, OnActivate and OnDeactivate; RxConductorBase did the same in ActivateItemAsync and DeactivateItemAsync. Both now forward it, so screen activation is cancellable. Command bodies reached through ReactiveCommand.CreateFromTask now take a required token, which binds its Func overload and gives them a real one. Config's own Command.Create path bottoms out in System.Windows.Input.ICommand.Execute, which returns void and has no token to offer, so those command types keep a file-scoped block that says so, and their call sites pass CancellationToken.None explicitly. Caliburn's IClose.TryCloseAsync and IEventAggregator declare no token and cannot be changed, so those are inline pragmas rather than fixes. --- src/ServiceControl.Config.Tests/.editorconfig | 6 -- ...tAggregationAutoSubscriptionModuleTests.cs | 4 +- src/ServiceControl.Config/.editorconfig | 16 +++-- .../ForceUpgradeAuditInstanceCommand.cs | 21 +++--- ...rceUpgradeServiceControlInstanceCommand.cs | 21 +++--- .../Commands/ScmuCommandChecks.cs | 34 ++++----- .../Commands/UpgradeAuditInstanceCommand.cs | 18 ++--- .../UpgradeServiceControlInstanceCommand.cs | 17 ++--- .../Framework/Modules/InstallerModule.cs | 17 ++--- .../Framework/RaygunFeedBack.cs | 24 ++++--- .../Framework/Rx/RxConductor.cs | 20 +++--- .../Framework/Rx/RxConductorBase.cs | 8 +-- .../Rx/RxConductorBaseWithActiveItem.cs | 7 +- .../Rx/RxConductorWithCollectionOneActive.cs | 34 ++++----- .../Framework/Rx/RxProgressScreen.cs | 2 +- .../Framework/Rx/RxScreen.cs | 16 +++-- .../Framework/ServiceControlWindowManager.cs | 55 +++++++------- .../ServiceControlAdvancedViewModel.cs | 10 +-- .../UI/FeedBack/FeedBackViewModel.cs | 12 +++- .../UI/InstanceAdd/MonitoringAddAttachment.cs | 15 ++-- .../ServiceControlAddAttachment.cs | 27 +++---- .../InstanceDetailsViewModel.cs | 8 +-- .../InstanceEdit/MonitoringEditAttachment.cs | 11 +-- .../ServiceControlAuditEditAttachment.cs | 11 +-- .../ServiceControlEditAttachment.cs | 11 +-- .../UI/License/LicenseViewModel.cs | 5 +- .../ListInstances/ListInstancesViewModel.cs | 6 +- .../UI/MessageBox/ExceptionMessageBox.xaml.cs | 3 + .../UI/Shell/FeedBackAttachment.cs | 5 +- .../UI/Shell/LicenseStatusManager.cs | 4 +- .../UI/Shell/ShellViewModel.cs | 26 ++++--- .../UI/Shell/VersionCheckerHelper.cs | 13 ++-- .../Upgrades/AddNewAuditInstanceAttachment.cs | 3 +- .../.editorconfig | 5 -- .../NewServiceControlAuditInstance.cs | 5 +- .../NewMonitoringInstance.cs | 5 +- .../NewServiceControlInstance.cs | 5 +- .../Validation/PowerShellCommandChecks.cs | 17 ++--- .../RunEngineTasksExplicitly.cs | 4 +- .../.editorconfig | 6 +- .../Instances/MonitoringInstance.cs | 5 +- .../Instances/MonitoringNewInstance.cs | 5 +- .../Instances/ServiceControlBaseService.cs | 7 +- .../ServiceControlInstallableBase.cs | 9 +-- .../Instances/ServiceControlInstance.cs | 5 +- .../Unattended/UnattendAuditInstaller.cs | 5 +- .../Unattended/UnattendMonitoringInstaller.cs | 9 +-- .../UnattendServiceControlInstaller.cs | 9 +-- .../Validation/AbstractCommandChecks.cs | 71 ++++++++++--------- .../Validation/PathsValidator.cs | 17 +++-- 50 files changed, 367 insertions(+), 312 deletions(-) diff --git a/src/ServiceControl.Config.Tests/.editorconfig b/src/ServiceControl.Config.Tests/.editorconfig index 235eef7320..c5410d8c02 100644 --- a/src/ServiceControl.Config.Tests/.editorconfig +++ b/src/ServiceControl.Config.Tests/.editorconfig @@ -6,9 +6,3 @@ dotnet_diagnostic.CA2007.severity = none # Justification: Executable specifications intentionally assign properties after the # object initializer to mirror user interaction order (e.g. typing a name after load) dotnet_diagnostic.IDE0017.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.PS0013.severity = none diff --git a/src/ServiceControl.Config.Tests/EventAggregationAutoSubscriptionModuleTests.cs b/src/ServiceControl.Config.Tests/EventAggregationAutoSubscriptionModuleTests.cs index 7db70027fe..b378faae8c 100644 --- a/src/ServiceControl.Config.Tests/EventAggregationAutoSubscriptionModuleTests.cs +++ b/src/ServiceControl.Config.Tests/EventAggregationAutoSubscriptionModuleTests.cs @@ -40,7 +40,7 @@ class FakeEvent; class FakeEventHandler : IHandle { - public Task HandleAsync(FakeEvent message, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task HandleAsync(FakeEvent message, CancellationToken cancellationToken = default) => throw new NotImplementedException(); } class FakeNonEventHandler; @@ -51,11 +51,13 @@ class FakeEventAggregator : IEventAggregator public bool HandlerExistsFor(Type messageType) => throw new NotImplementedException(); +#pragma warning disable PS0013 // Caliburn.Micro's IEventAggregator declares the marshal delegate as Func, Task> public void Subscribe(object subscriber, Func, Task> marshal) => Subscribers.Add(subscriber); public void Unsubscribe(object subscriber) => throw new NotImplementedException(); public Task PublishAsync(object message, Func, Task> marshal, CancellationToken cancellationToken = new CancellationToken()) => throw new NotImplementedException(); +#pragma warning restore PS0013 } } } \ No newline at end of file diff --git a/src/ServiceControl.Config/.editorconfig b/src/ServiceControl.Config/.editorconfig index d2502b8251..6181205b06 100644 --- a/src/ServiceControl.Config/.editorconfig +++ b/src/ServiceControl.Config/.editorconfig @@ -3,10 +3,14 @@ # Justification: Application synchronization contexts don't require ConfigureAwait(false) dotnet_diagnostic.CA2007.severity = none -# may be enabled in future -dotnet_diagnostic.PS0003.severity = none # A parameter of type CancellationToken on a non-private delegate or method should be optional -dotnet_diagnostic.PS0013.severity = none # A Func used as a method parameter with a Task, ValueTask, or ValueTask return type argument should have at least one CancellationToken parameter type argument unless it has a parameter type argument implementing ICancellableContext -dotnet_diagnostic.PS0018.severity = none # A task-returning method should have a CancellationToken parameter unless it has a parameter implementing ICancellableContext - # Use auto property - should be fixed in an independent PR -dotnet_diagnostic.IDE0032.severity = suggestion \ No newline at end of file +dotnet_diagnostic.IDE0032.severity = suggestion + +# System.Windows.Input.ICommand.Execute(object) returns void and supplies no CancellationToken, +# so the async command chain built on it has no token to accept or forward. A token added here +# could only ever be CancellationToken.None. Command bodies themselves do take tokens: they are +# wired through ReactiveCommand.CreateFromTask(Func), which supplies a +# real one. +[{Framework/Commands/*.cs,Framework/Command.cs,Commands/SelectPathCommand.cs}] +dotnet_diagnostic.PS0013.severity = none +dotnet_diagnostic.PS0018.severity = none diff --git a/src/ServiceControl.Config/Commands/ForceUpgradeAuditInstanceCommand.cs b/src/ServiceControl.Config/Commands/ForceUpgradeAuditInstanceCommand.cs index c500588817..341c3a4815 100644 --- a/src/ServiceControl.Config/Commands/ForceUpgradeAuditInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/ForceUpgradeAuditInstanceCommand.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Commands; using System.IO; +using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -36,34 +37,34 @@ public override async Task ExecuteAsync(ServiceControlAdvancedViewModel model) return; } - await UpgradeServiceControlInstance(model, instance, new ServiceControlUpgradeOptions()); + await UpgradeServiceControlInstance(model, instance, new ServiceControlUpgradeOptions(), CancellationToken.None); await eventAggregator.PublishOnUIThreadAsync(new ResetInstances()); } - async Task UpgradeServiceControlInstance(ServiceControlAdvancedViewModel model, ServiceControlAuditInstance instance, ServiceControlUpgradeOptions upgradeOptions) + async Task UpgradeServiceControlInstance(ServiceControlAdvancedViewModel model, ServiceControlAuditInstance instance, ServiceControlUpgradeOptions upgradeOptions, CancellationToken cancellationToken) { using (var progress = model.GetProgressObject($"UPGRADING {model.Name}")) { var reportCard = new ReportCard(); var restartAgain = model.IsRunning; - var stopped = await model.StopService(progress); + var stopped = await model.StopService(progress, cancellationToken); if (!stopped) { - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); reportCard.Errors.Add("Failed to stop the service"); reportCard.SetStatus(); - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", cancellationToken: cancellationToken); return; } if (Directory.Exists(model.ForcedUpgradeBackupLocation)) { - await windowManager.ShowMessage("Cannot make database backup.", $"The target database backup location: {model.ForcedUpgradeBackupLocation} already exists.", hideCancel: true); + await windowManager.ShowMessage("Cannot make database backup.", $"The target database backup location: {model.ForcedUpgradeBackupLocation} already exists.", hideCancel: true, cancellationToken: cancellationToken); return; } @@ -78,18 +79,18 @@ async Task UpgradeServiceControlInstance(ServiceControlAdvancedViewModel model, if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:", cancellationToken: cancellationToken); return; } if (restartAgain) { - var serviceStarted = await model.StartService(progress, maintenanceMode: false); + var serviceStarted = await model.StartService(progress, maintenanceMode: false, cancellationToken); if (!serviceStarted) { reportCard.Errors.Add("The Service failed to start. Please consult the ServiceControl logs for this instance"); - await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", "Instance reported this error after upgrade:"); + await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", "Instance reported this error after upgrade:", cancellationToken: cancellationToken); return; } @@ -97,7 +98,7 @@ async Task UpgradeServiceControlInstance(ServiceControlAdvancedViewModel model, } await model.TryCloseAsync(true); - await eventAggregator.PublishOnUIThreadAsync(new ResetInstances()); + await eventAggregator.PublishOnUIThreadAsync(new ResetInstances(), cancellationToken); } readonly IEventAggregator eventAggregator; diff --git a/src/ServiceControl.Config/Commands/ForceUpgradeServiceControlInstanceCommand.cs b/src/ServiceControl.Config/Commands/ForceUpgradeServiceControlInstanceCommand.cs index 40992c6083..b3d8cb3549 100644 --- a/src/ServiceControl.Config/Commands/ForceUpgradeServiceControlInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/ForceUpgradeServiceControlInstanceCommand.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Commands; using System.IO; +using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -36,34 +37,34 @@ public override async Task ExecuteAsync(ServiceControlAdvancedViewModel model) return; } - await UpgradeServiceControlInstance(model, instance); + await UpgradeServiceControlInstance(model, instance, CancellationToken.None); await eventAggregator.PublishOnUIThreadAsync(new ResetInstances()); } - async Task UpgradeServiceControlInstance(ServiceControlAdvancedViewModel model, ServiceControlInstance instance) + async Task UpgradeServiceControlInstance(ServiceControlAdvancedViewModel model, ServiceControlInstance instance, CancellationToken cancellationToken) { using (var progress = model.GetProgressObject($"UPGRADING {model.Name}")) { var reportCard = new ReportCard(); var restartAgain = model.IsRunning; - var stopped = await model.StopService(progress); + var stopped = await model.StopService(progress, cancellationToken); if (!stopped) { - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); reportCard.Errors.Add("Failed to stop the service"); reportCard.SetStatus(); - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", cancellationToken: cancellationToken); return; } if (Directory.Exists(model.ForcedUpgradeBackupLocation)) { - await windowManager.ShowMessage("Cannot make database backup.", $"The target database backup location: {model.ForcedUpgradeBackupLocation} already exists.", hideCancel: true); + await windowManager.ShowMessage("Cannot make database backup.", $"The target database backup location: {model.ForcedUpgradeBackupLocation} already exists.", hideCancel: true, cancellationToken: cancellationToken); return; } @@ -78,20 +79,20 @@ async Task UpgradeServiceControlInstance(ServiceControlAdvancedViewModel model, if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:", cancellationToken: cancellationToken); return; } if (restartAgain) { - var serviceStarted = await model.StartService(progress, maintenanceMode: false); + var serviceStarted = await model.StartService(progress, maintenanceMode: false, cancellationToken); if (!serviceStarted) { reportCard.Errors.Add( "The Service failed to start. Please consult the ServiceControl logs for this instance"); await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", - "Instance reported this error after upgrade:"); + "Instance reported this error after upgrade:", cancellationToken: cancellationToken); return; } @@ -99,7 +100,7 @@ await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", } await model.TryCloseAsync(true); - await eventAggregator.PublishOnUIThreadAsync(new ResetInstances()); + await eventAggregator.PublishOnUIThreadAsync(new ResetInstances(), cancellationToken); } readonly IEventAggregator eventAggregator; diff --git a/src/ServiceControl.Config/Commands/ScmuCommandChecks.cs b/src/ServiceControl.Config/Commands/ScmuCommandChecks.cs index b21e0b37fd..5b47955af4 100644 --- a/src/ServiceControl.Config/Commands/ScmuCommandChecks.cs +++ b/src/ServiceControl.Config/Commands/ScmuCommandChecks.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; using System.Text; + using System.Threading; using System.Threading.Tasks; using ServiceControl.Config.Framework; using ServiceControlInstaller.Engine; @@ -18,7 +19,7 @@ public ScmuCommandChecks(IServiceControlWindowManager windowManager) this.windowManager = windowManager; } - protected override async Task PromptForRabbitMqCheck(bool isUpgrade) + protected override async Task PromptForRabbitMqCheck(bool isUpgrade, CancellationToken cancellationToken = default) { var title = isUpgrade ? "UPGRADE WARNING" : "INSTALL WARNING"; var beforeWhat = isUpgrade ? "upgrading" : "installing"; @@ -34,27 +35,28 @@ protected override async Task PromptForRabbitMqCheck(bool isUpgrade) message.AppendLine(); message.AppendLine($"Please confirm your broker meets the minimum requirements before {beforeWhat}."); - var continueInstall = await windowManager.ShowYesNoDialog(title, message.ToString(), question, yes, no); + var continueInstall = await windowManager.ShowYesNoDialog(title, message.ToString(), question, yes, no, cancellationToken); return continueInstall; } - protected override Task NotifyForDeprecatedMessageTransport(TransportInfo transport) + protected override Task NotifyForDeprecatedMessageTransport(TransportInfo transport, CancellationToken cancellationToken = default) { - return windowManager.ShowMessage("DEPRECATED MESSAGE TRANSPORT", $"The message transport '{transport.DisplayName}' is not available in this version of ServiceControl, and this instance cannot be upgraded.", acceptText: "Cancel Upgrade", hideCancel: true); + return windowManager.ShowMessage("DEPRECATED MESSAGE TRANSPORT", $"The message transport '{transport.DisplayName}' is not available in this version of ServiceControl, and this instance cannot be upgraded.", acceptText: "Cancel Upgrade", hideCancel: true, cancellationToken: cancellationToken); } - protected override Task NotifyForMissingSystemPrerequisites(string missingPrereqsMessage) + protected override Task NotifyForMissingSystemPrerequisites(string missingPrereqsMessage, CancellationToken cancellationToken = default) { - return windowManager.ShowMessage("Missing prerequisites", missingPrereqsMessage, acceptText: "Cancel", hideCancel: true); + return windowManager.ShowMessage("Missing prerequisites", missingPrereqsMessage, acceptText: "Cancel", hideCancel: true, cancellationToken: cancellationToken); } - protected override async Task NotifyForIncompatibleStorageEngine(IServiceControlBaseInstance baseInstance) + protected override async Task NotifyForIncompatibleStorageEngine(IServiceControlBaseInstance baseInstance, CancellationToken cancellationToken = default) { var openUpgradeGuide = await windowManager.ShowYesNoDialog("STORAGE ENGINE INCOMPATIBLE", $"The storage format has changed and the {baseInstance.PersistenceManifest.DisplayName} storage engine is no longer available. Upgrading requires a side-by-side deployment of both versions. Migration guidance is available in the version 4 to 5 upgrade guidance at {UpgradeGuide4to5Url}", "Open online ServiceControl 4 to 5 upgrade guide in system default browser?", "Yes", - "No" + "No", + cancellationToken ); if (openUpgradeGuide) @@ -63,7 +65,7 @@ protected override async Task NotifyForIncompatibleStorageEngine(IServiceControl } } - protected override async Task NotifyForIncompatibleUpgradeVersion(UpgradeInfo upgradeInfo) + protected override async Task NotifyForIncompatibleUpgradeVersion(UpgradeInfo upgradeInfo, CancellationToken cancellationToken = default) { var nextVersion = upgradeInfo.UpgradePath[0]; await windowManager.ShowMessage("VERSION UPGRADE INCOMPATIBLE", @@ -76,26 +78,26 @@ await windowManager.ShowMessage("VERSION UPGRADE INCOMPATIBLE", "Upgrade this instance to the latest version of ServiceControl.\r\n" + "\r\n" + "", - hideCancel: true); + hideCancel: true, cancellationToken: cancellationToken); } - protected override Task NotifyError(string title, string message) + protected override Task NotifyError(string title, string message, CancellationToken cancellationToken = default) { - return windowManager.ShowMessage(title.ToUpperInvariant(), message, hideCancel: true); + return windowManager.ShowMessage(title.ToUpperInvariant(), message, hideCancel: true, cancellationToken: cancellationToken); } - protected override Task PromptToStopRunningInstance(BaseService instance) + protected override Task PromptToStopRunningInstance(BaseService instance, CancellationToken cancellationToken = default) { return windowManager.ShowYesNoDialog($"STOP INSTANCE AND UPGRADE TO {Constants.CurrentVersion}", $"{instance.Name} needs to be stopped in order to upgrade to version {Constants.CurrentVersion}.", "Do you want to proceed?", - "Yes, I want to proceed", "No"); + "Yes, I want to proceed", "No", cancellationToken); } - protected override Task PromptToContinueWithForcedUpgrade() + protected override Task PromptToContinueWithForcedUpgrade(CancellationToken cancellationToken = default) { return windowManager.ShowMessage("Forced migration", - "Do you want to proceed with forced migration to ServiceControl 5? The current RavenDB 3.5 database will be moved aside and a new database will be created.", "Yes"); + "Do you want to proceed with forced migration to ServiceControl 5? The current RavenDB 3.5 database will be moved aside and a new database will be created.", "Yes", cancellationToken: cancellationToken); } } } diff --git a/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs b/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs index 1c0d966a94..6ddcb8295c 100644 --- a/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/UpgradeAuditInstanceCommand.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Commands { using System; + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -46,7 +47,7 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model) return; } - await UpgradeAuditInstance(model, instance, upgradeOptions); + await UpgradeAuditInstance(model, instance, upgradeOptions, CancellationToken.None); await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); } @@ -54,7 +55,8 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model) async Task UpgradeAuditInstance( InstanceDetailsViewModel model, ServiceControlAuditInstance instance, - ServiceControlUpgradeOptions upgradeOptions + ServiceControlUpgradeOptions upgradeOptions, + CancellationToken cancellationToken ) { using (var progress = model.GetProgressObject($"UPGRADING {model.Name}")) @@ -62,15 +64,15 @@ ServiceControlUpgradeOptions upgradeOptions var reportCard = new ReportCard(); var restartAgain = model.IsRunning; - var stopped = await model.StopService(progress); + var stopped = await model.StopService(progress, cancellationToken); if (!stopped) { - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); reportCard.Errors.Add("Failed to stop the service"); reportCard.SetStatus(); - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", cancellationToken: cancellationToken); return; } @@ -79,17 +81,17 @@ ServiceControlUpgradeOptions upgradeOptions if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:", cancellationToken: cancellationToken); return; } if (restartAgain) { - var serviceStarted = await model.StartService(progress); + var serviceStarted = await model.StartService(progress, cancellationToken); if (!serviceStarted) { reportCard.Errors.Add("The Service failed to start. Please consult the ServiceControl logs for this instance"); - await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", "Instance reported this error after upgrade:"); + await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", "Instance reported this error after upgrade:", cancellationToken: cancellationToken); } } } diff --git a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs index 26f2671b7e..fc7bdc26f0 100644 --- a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Commands { using System; + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -146,27 +147,27 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model) return; } - await UpgradeServiceControlInstance(model, instance, upgradeOptions); + await UpgradeServiceControlInstance(model, instance, upgradeOptions, CancellationToken.None); await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); } - async Task UpgradeServiceControlInstance(InstanceDetailsViewModel model, ServiceControlInstance instance, ServiceControlUpgradeOptions upgradeOptions) + async Task UpgradeServiceControlInstance(InstanceDetailsViewModel model, ServiceControlInstance instance, ServiceControlUpgradeOptions upgradeOptions, CancellationToken cancellationToken) { using (var progress = model.GetProgressObject($"UPGRADING {model.Name}")) { var reportCard = new ReportCard(); var restartAgain = model.IsRunning; - var stopped = await model.StopService(progress); + var stopped = await model.StopService(progress, cancellationToken); if (!stopped) { - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); reportCard.Errors.Add("Failed to stop the service"); reportCard.SetStatus(); - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", cancellationToken: cancellationToken); return; } @@ -175,17 +176,17 @@ async Task UpgradeServiceControlInstance(InstanceDetailsViewModel model, Service if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES UPGRADING INSTANCE", "Could not upgrade instance because of the following errors:", "There were some warnings while upgrading the instance:", cancellationToken: cancellationToken); } else { if (restartAgain) { - var serviceStarted = await model.StartService(progress); + var serviceStarted = await model.StartService(progress, cancellationToken); if (!serviceStarted) { reportCard.Errors.Add("The Service failed to start. Please consult the ServiceControl logs for this instance"); - await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", "Instance reported this error after upgrade:"); + await windowManager.ShowActionReport(reportCard, "UPGRADE FAILURE", "Instance reported this error after upgrade:", cancellationToken: cancellationToken); } } } diff --git a/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs b/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs index 531997dba8..8bbe5c60cb 100644 --- a/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs +++ b/src/ServiceControl.Config/Framework/Modules/InstallerModule.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.Framework.Modules { using System; + using System.Threading; using System.Threading.Tasks; using Autofac; using ServiceControlInstaller.Engine.FileSystem; @@ -43,7 +44,7 @@ public abstract class InstallerBase public class ServiceControlInstallerBase : InstallerBase { - internal async Task Add(ServiceControlInstallableBase details, IProgress progress, Func> promptToProceed) + internal async Task Add(ServiceControlInstallableBase details, IProgress progress, Func> promptToProceed, CancellationToken cancellationToken = default) { ZipInfo.ValidateZip(); @@ -51,7 +52,7 @@ internal async Task Add(ServiceControlInstallableBase details, IProg instanceInstaller.ReportCard = new ReportCard(); //Validation - await instanceInstaller.Validate(promptToProceed); + await instanceInstaller.Validate(promptToProceed, cancellationToken); if (instanceInstaller.ReportCard.HasErrors || instanceInstaller.ReportCard.CancelRequested) { instanceInstaller.ReportCard.Status = Status.FailedValidation; @@ -148,12 +149,12 @@ protected virtual void UpgradeOptions(ServiceControlUpgradeOptions upgradeOption upgradeOptions.ApplyChangesToInstance(instance); } - internal async Task Update(ServiceControlBaseService instance, bool startService) + internal async Task Update(ServiceControlBaseService instance, bool startService, CancellationToken cancellationToken = default) { try { instance.ReportCard = new ReportCard(); - await instance.ValidateChanges(); + await instance.ValidateChanges(cancellationToken); if (instance.ReportCard.HasErrors) { instance.ReportCard.Status = Status.FailedValidation; @@ -238,7 +239,7 @@ public MonitoringInstanceInstaller() ZipInfo = new PlatformZipInfo(Constants.MonitoringExe, "ServiceControl Monitoring", "Particular.ServiceControl.Monitoring.zip"); } - internal async Task Add(MonitoringNewInstance details, IProgress progress, Func> promptToProceed) + internal async Task Add(MonitoringNewInstance details, IProgress progress, Func> promptToProceed, CancellationToken cancellationToken = default) { ZipInfo.ValidateZip(); @@ -246,7 +247,7 @@ internal async Task Add(MonitoringNewInstance details, IProgress prog return instance.ReportCard; } - internal async Task Update(MonitoringInstance instance, bool startService) + internal async Task Update(MonitoringInstance instance, bool startService, CancellationToken cancellationToken = default) { try { instance.ReportCard = new ReportCard(); - await instance.ValidateChanges(); + await instance.ValidateChanges(cancellationToken); if (instance.ReportCard.HasErrors) { instance.ReportCard.Status = Status.FailedValidation; diff --git a/src/ServiceControl.Config/Framework/RaygunFeedBack.cs b/src/ServiceControl.Config/Framework/RaygunFeedBack.cs index 6128a68511..b4e62316c9 100644 --- a/src/ServiceControl.Config/Framework/RaygunFeedBack.cs +++ b/src/ServiceControl.Config/Framework/RaygunFeedBack.cs @@ -7,6 +7,7 @@ namespace ServiceControl.Config.Framework using System.Net; using System.Net.Http; using System.Reflection; + using System.Threading; using System.Threading.Tasks; using Extensions; using Mindscape.Raygun4Net; @@ -21,9 +22,9 @@ public RaygunFeedback() init = Task.Run(async () => { - enabled = await TryInitializeRaygunClientWithCredentials() || - await TryInitializeRaygunClientWithCredentials(CredentialCache.DefaultCredentials) || - await TryInitializeRaygunClientWithCredentials(CredentialCache.DefaultNetworkCredentials); + enabled = await TryInitializeRaygunClientWithCredentials(null, CancellationToken.None) || + await TryInitializeRaygunClientWithCredentials(CredentialCache.DefaultCredentials, CancellationToken.None) || + await TryInitializeRaygunClientWithCredentials(CredentialCache.DefaultNetworkCredentials, CancellationToken.None); version = GetVersion(); }); } @@ -45,7 +46,7 @@ static Guid GetOrSetTrackingId() return trackingId; } - public Task SendFeedBack(string emailAddress, string message, bool includeSystemInfo) + public Task SendFeedBack(string emailAddress, string message, bool includeSystemInfo, CancellationToken cancellationToken = default) { var userInfo = ((IRaygunUserProvider)this).GetUser()!; userInfo.Email = emailAddress; @@ -62,10 +63,10 @@ public Task SendFeedBack(string emailAddress, string message, bool includeSystem } var m = raygunMessage.Build(); - return raygunClient.Send(m); + return raygunClient.Send(m, cancellationToken); } - public Task SendException(Exception ex, bool includeSystemInfo) + public Task SendException(Exception ex, bool includeSystemInfo, CancellationToken cancellationToken = default) { var userInfo = ((IRaygunUserProvider)this).GetUser()!; @@ -81,7 +82,7 @@ public Task SendException(Exception ex, bool includeSystemInfo) } var m = raygunMessage.Build(); - return raygunClient.Send(m); + return raygunClient.Send(m, cancellationToken); } RaygunIdentifierMessage IRaygunUserProvider.GetUser() => new(trackingId.BareString()) @@ -102,7 +103,7 @@ public bool Enabled } } - async Task TryInitializeRaygunClientWithCredentials(ICredentials? credentials = default) + async Task TryInitializeRaygunClientWithCredentials(ICredentials? credentials, CancellationToken cancellationToken) { HttpClient? http = null; try @@ -111,12 +112,17 @@ async Task TryInitializeRaygunClientWithCredentials(ICredentials? credenti { Timeout = TimeSpan.FromSeconds(5) }; - using var response = await http.GetAsync(RaygunUrl); + using var response = await http.GetAsync(RaygunUrl, cancellationToken); response.EnsureSuccessStatusCode(); raygunClient = new RaygunClient(raygunSettings, http, this); return true; } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + http?.Dispose(); + throw; + } catch { http?.Dispose(); diff --git a/src/ServiceControl.Config/Framework/Rx/RxConductor.cs b/src/ServiceControl.Config/Framework/Rx/RxConductor.cs index db071efcde..fb912b2ac4 100644 --- a/src/ServiceControl.Config/Framework/Rx/RxConductor.cs +++ b/src/ServiceControl.Config/Framework/Rx/RxConductor.cs @@ -7,23 +7,23 @@ public partial class RxConductor : RxConductorBaseWithActiveItem where T : class { - public override async Task ActivateItem(T item) + public override async Task ActivateItem(T item, CancellationToken cancellationToken = default) { if (item != null && item.Equals(ActiveItem)) { if (IsActive) { - await ScreenExtensions.TryActivateAsync(item); + await ScreenExtensions.TryActivateAsync(item, cancellationToken); OnActivationProcessed(item, true); } return; } - var result = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }); + var result = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, cancellationToken); if (result.CloseCanOccur) { - await ChangeActiveItem(item, true); + await ChangeActiveItem(item, true, cancellationToken); } else { @@ -31,29 +31,29 @@ public override async Task ActivateItem(T item) } } - public override async Task DeactivateItem(T item, bool close) + public override async Task DeactivateItem(T item, bool close, CancellationToken cancellationToken = default) { if (item == null || !item.Equals(ActiveItem)) { return; } - var result = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }); + var result = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, cancellationToken); if (result.CloseCanOccur) { - await ChangeActiveItem(default, close); + await ChangeActiveItem(default, close, cancellationToken); } } - public override async Task CanCloseAsync(CancellationToken cancellationToken) + public override async Task CanCloseAsync(CancellationToken cancellationToken = default) { var result = await CloseStrategy.ExecuteAsync(new[] { ActiveItem }, cancellationToken); return result.CloseCanOccur; } - protected override Task OnActivate() => ScreenExtensions.TryActivateAsync(ActiveItem); + protected override Task OnActivate(CancellationToken cancellationToken = default) => ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); - protected override Task OnDeactivate(bool close) => ScreenExtensions.TryDeactivateAsync(ActiveItem, close); + protected override Task OnDeactivate(bool close, CancellationToken cancellationToken = default) => ScreenExtensions.TryDeactivateAsync(ActiveItem, close, cancellationToken); public override IEnumerable GetChildren() { diff --git a/src/ServiceControl.Config/Framework/Rx/RxConductorBase.cs b/src/ServiceControl.Config/Framework/Rx/RxConductorBase.cs index 7dc66ca48d..7d38d94cc4 100644 --- a/src/ServiceControl.Config/Framework/Rx/RxConductorBase.cs +++ b/src/ServiceControl.Config/Framework/Rx/RxConductorBase.cs @@ -17,12 +17,12 @@ public ICloseStrategy CloseStrategy Task IConductor.ActivateItemAsync(object item, CancellationToken cancellationToken) { - return ActivateItem((T)item); + return ActivateItem((T)item, cancellationToken); } Task IConductor.DeactivateItemAsync(object item, bool close, CancellationToken cancellationToken) { - return DeactivateItem((T)item, close); + return DeactivateItem((T)item, close, cancellationToken); } IEnumerable IParent.GetChildren() @@ -34,9 +34,9 @@ IEnumerable IParent.GetChildren() public abstract IEnumerable GetChildren(); - public abstract Task ActivateItem(T item); + public abstract Task ActivateItem(T item, CancellationToken cancellationToken = default); - public abstract Task DeactivateItem(T item, bool close); + public abstract Task DeactivateItem(T item, bool close, CancellationToken cancellationToken = default); protected virtual void OnActivationProcessed(T item, bool success) { diff --git a/src/ServiceControl.Config/Framework/Rx/RxConductorBaseWithActiveItem.cs b/src/ServiceControl.Config/Framework/Rx/RxConductorBaseWithActiveItem.cs index 6e3669f930..5b6107fe2b 100644 --- a/src/ServiceControl.Config/Framework/Rx/RxConductorBaseWithActiveItem.cs +++ b/src/ServiceControl.Config/Framework/Rx/RxConductorBaseWithActiveItem.cs @@ -1,5 +1,6 @@ namespace ServiceControl.Config.Framework.Rx { + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; @@ -17,15 +18,15 @@ object IHaveActiveItem.ActiveItem set { ActiveItem = (T)value; } } - protected virtual async Task ChangeActiveItem(T newItem, bool closePrevious) + protected virtual async Task ChangeActiveItem(T newItem, bool closePrevious, CancellationToken cancellationToken = default) { - await ScreenExtensions.TryDeactivateAsync(activeItem, closePrevious); + await ScreenExtensions.TryDeactivateAsync(activeItem, closePrevious, cancellationToken); newItem = EnsureItem(newItem); if (IsActive) { - await ScreenExtensions.TryActivateAsync(newItem); + await ScreenExtensions.TryActivateAsync(newItem, cancellationToken); } activeItem = newItem; diff --git a/src/ServiceControl.Config/Framework/Rx/RxConductorWithCollectionOneActive.cs b/src/ServiceControl.Config/Framework/Rx/RxConductorWithCollectionOneActive.cs index 95cb7a932a..375cd87aaa 100644 --- a/src/ServiceControl.Config/Framework/Rx/RxConductorWithCollectionOneActive.cs +++ b/src/ServiceControl.Config/Framework/Rx/RxConductorWithCollectionOneActive.cs @@ -48,23 +48,23 @@ public override IEnumerable GetChildren() return items; } - public override async Task ActivateItem(T item) + public override async Task ActivateItem(T item, CancellationToken cancellationToken = default) { if (item != null && item.Equals(ActiveItem)) { if (IsActive) { - await ScreenExtensions.TryActivateAsync(item); + await ScreenExtensions.TryActivateAsync(item, cancellationToken); OnActivationProcessed(item, true); } return; } - await ChangeActiveItem(item, false); + await ChangeActiveItem(item, false, cancellationToken); } - public override async Task DeactivateItem(T item, bool close) + public override async Task DeactivateItem(T item, bool close, CancellationToken cancellationToken = default) { if (item == null) { @@ -73,30 +73,30 @@ public override async Task DeactivateItem(T item, bool close) if (!close) { - await ScreenExtensions.TryDeactivateAsync(item, false); + await ScreenExtensions.TryDeactivateAsync(item, false, cancellationToken); } else { - var result = await CloseStrategy.ExecuteAsync(new[] { item }); + var result = await CloseStrategy.ExecuteAsync(new[] { item }, cancellationToken); if (result.CloseCanOccur) { - await CloseItemCore(item); + await CloseItemCore(item, cancellationToken); } } } - async Task CloseItemCore(T item) + async Task CloseItemCore(T item, CancellationToken cancellationToken) { if (item.Equals(ActiveItem)) { var index = items.IndexOf(item); var next = DetermineNextItemToActivate(items, index); - await ChangeActiveItem(next, true); + await ChangeActiveItem(next, true, cancellationToken); } else { - await ScreenExtensions.TryDeactivateAsync(item, true); + await ScreenExtensions.TryDeactivateAsync(item, true, cancellationToken); } items.Remove(item); @@ -119,7 +119,7 @@ protected virtual T DetermineNextItemToActivate(IList list, int lastIndex) return default; } - public override async Task CanCloseAsync(CancellationToken cancellationToken) + public override async Task CanCloseAsync(CancellationToken cancellationToken = default) { var result = await CloseStrategy.ExecuteAsync(items.ToList(), cancellationToken); var canClose = result.CloseCanOccur; @@ -140,7 +140,7 @@ public override async Task CanCloseAsync(CancellationToken cancellationTok while (closable.Contains(next)); var previousActive = ActiveItem; - await ChangeActiveItem(next, true); + await ChangeActiveItem(next, true, cancellationToken); items.Remove(previousActive); var stillToClose = closable.ToList(); @@ -150,7 +150,7 @@ public override async Task CanCloseAsync(CancellationToken cancellationTok await Task.WhenAll( from deactivatable in closable.OfType() - select deactivatable.DeactivateAsync(true) + select deactivatable.DeactivateAsync(true, cancellationToken) ); items.RemoveRange(closable); @@ -159,9 +159,9 @@ select deactivatable.DeactivateAsync(true) return canClose; } - protected override Task OnActivate() => ScreenExtensions.TryActivateAsync(ActiveItem); + protected override Task OnActivate(CancellationToken cancellationToken = default) => ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); - protected override async Task OnDeactivate(bool close) + protected override async Task OnDeactivate(bool close, CancellationToken cancellationToken = default) { if (close) { @@ -169,14 +169,14 @@ protected override async Task OnDeactivate(bool close) { if (item is IDeactivate deactivatable) { - await deactivatable.DeactivateAsync(true); + await deactivatable.DeactivateAsync(true, cancellationToken); } } items.Clear(); } else { - await ScreenExtensions.TryDeactivateAsync(ActiveItem, false); + await ScreenExtensions.TryDeactivateAsync(ActiveItem, false, cancellationToken); } } diff --git a/src/ServiceControl.Config/Framework/Rx/RxProgressScreen.cs b/src/ServiceControl.Config/Framework/Rx/RxProgressScreen.cs index 5453f17cd7..736fb942f6 100644 --- a/src/ServiceControl.Config/Framework/Rx/RxProgressScreen.cs +++ b/src/ServiceControl.Config/Framework/Rx/RxProgressScreen.cs @@ -47,7 +47,7 @@ public int ProgressPercent } } - public override Task CanCloseAsync(CancellationToken cancellationToken) => Task.FromResult(!InProgress); + public override Task CanCloseAsync(CancellationToken cancellationToken = default) => Task.FromResult(!InProgress); void NotifyUpdates() { diff --git a/src/ServiceControl.Config/Framework/Rx/RxScreen.cs b/src/ServiceControl.Config/Framework/Rx/RxScreen.cs index 44b7a33f5f..7d5a91b6d9 100644 --- a/src/ServiceControl.Config/Framework/Rx/RxScreen.cs +++ b/src/ServiceControl.Config/Framework/Rx/RxScreen.cs @@ -63,12 +63,12 @@ async Task IActivate.ActivateAsync(CancellationToken cancellationToken) if (!IsInitialized) { IsInitialized = initialized = true; - await OnInitialize(); + await OnInitialize(cancellationToken); } IsActive = true; Log.Info("Activating {0}.", this); - await OnActivate(); + await OnActivate(cancellationToken); await Activated(this, new ActivationEventArgs { @@ -87,7 +87,7 @@ async Task IDeactivate.DeactivateAsync(bool close, CancellationToken cancellatio IsActive = false; Log.Info("Deactivating {0}.", this); - await OnDeactivate(close); + await OnDeactivate(close, cancellationToken); await Deactivated(this, new DeactivationEventArgs { @@ -106,14 +106,16 @@ async Task IDeactivate.DeactivateAsync(bool close, CancellationToken cancellatio /// Called to check whether or not this instance can close. /// /// The implementor calls this action with the result of the close check. - public virtual Task CanCloseAsync(CancellationToken cancellationToken) => Task.FromResult(true); + public virtual Task CanCloseAsync(CancellationToken cancellationToken = default) => Task.FromResult(true); /// /// Tries to close this instance by asking its Parent to initiate shutdown or by asking its corresponding view to close. /// Also provides an opportunity to pass a dialog result to it's corresponding view. /// /// The dialog result. +#pragma warning disable PS0018 // Caliburn.Micro's IClose.TryCloseAsync declares no CancellationToken, so one cannot be added here public virtual Task TryCloseAsync(bool? dialogResult = null) +#pragma warning restore PS0018 { Result = dialogResult; @@ -141,18 +143,18 @@ static bool IsHandler(object obj) /// /// Called when initializing. /// - protected virtual Task OnInitialize() => Task.CompletedTask; + protected virtual Task OnInitialize(CancellationToken cancellationToken = default) => Task.CompletedTask; /// /// Called when activating. /// - protected virtual Task OnActivate() => Task.CompletedTask; + protected virtual Task OnActivate(CancellationToken cancellationToken = default) => Task.CompletedTask; /// /// Called when deactivating. /// /// Inidicates whether this instance will be closed. - protected virtual Task OnDeactivate(bool close) => Task.CompletedTask; + protected virtual Task OnDeactivate(bool close, CancellationToken cancellationToken = default) => Task.CompletedTask; static readonly ILog Log = LogManager.GetLog(typeof(Screen)); } diff --git a/src/ServiceControl.Config/Framework/ServiceControlWindowManager.cs b/src/ServiceControl.Config/Framework/ServiceControlWindowManager.cs index 5de1ec8bdc..cff3833016 100644 --- a/src/ServiceControl.Config/Framework/ServiceControlWindowManager.cs +++ b/src/ServiceControl.Config/Framework/ServiceControlWindowManager.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using System.Windows; using Caliburn.Micro; @@ -13,23 +14,23 @@ public interface IServiceControlWindowManager : IWindowManager { - Task NavigateTo(RxScreen screen, object context = null, IDictionary settings = null); + Task NavigateTo(RxScreen screen, object context = null, IDictionary settings = null, CancellationToken cancellationToken = default); - Task ShowInnerDialog(RxScreen screen, object context = null, IDictionary settings = null); + Task ShowInnerDialog(RxScreen screen, object context = null, IDictionary settings = null, CancellationToken cancellationToken = default); - Task ShowOverlayDialog(RxScreen screen, object context = null, IDictionary settings = null); + Task ShowOverlayDialog(RxScreen screen, object context = null, IDictionary settings = null, CancellationToken cancellationToken = default); - Task ShowMessage(string title, string message, string acceptText = "Ok", bool hideCancel = false); + Task ShowMessage(string title, string message, string acceptText = "Ok", bool hideCancel = false, CancellationToken cancellationToken = default); - Task ShowYesNoCancelDialog(string title, string message, string question, string yesText, string noText); + Task ShowYesNoCancelDialog(string title, string message, string question, string yesText, string noText, CancellationToken cancellationToken = default); - Task ShowYesNoDialog(string title, string message, string question, string yesText, string noText); + Task ShowYesNoDialog(string title, string message, string question, string yesText, string noText, CancellationToken cancellationToken = default); - Task ShowSliderDialog(SliderDialogViewModel viewModel); + Task ShowSliderDialog(SliderDialogViewModel viewModel, CancellationToken cancellationToken = default); - Task ShowTextBoxDialog(TextBoxDialogViewModel viewModel); + Task ShowTextBoxDialog(TextBoxDialogViewModel viewModel, CancellationToken cancellationToken = default); - Task ShowActionReport(ReportCard reportcard, string title, string errorsMessage = "", string warningsMessage = ""); + Task ShowActionReport(ReportCard reportcard, string title, string errorsMessage = "", string warningsMessage = "", CancellationToken cancellationToken = default); void ScrollFirstErrorIntoView(object viewModel, object context = null); } @@ -41,22 +42,22 @@ public ServiceControlWindowManager(Func reportC this.reportCardViewModelFactory = reportCardViewModelFactory; } - public Task NavigateTo(RxScreen screen, object context = null, IDictionary settings = null) + public Task NavigateTo(RxScreen screen, object context = null, IDictionary settings = null, CancellationToken cancellationToken = default) { var shell = GetShell(); shell.ActiveContext = context; - return shell.ActivateItem(screen); + return shell.ActivateItem(screen, cancellationToken); } - public async Task ShowInnerDialog(RxScreen screen, object context = null, IDictionary settings = null) + public async Task ShowInnerDialog(RxScreen screen, object context = null, IDictionary settings = null, CancellationToken cancellationToken = default) { var shell = GetShell(); var previousContext = shell.ActiveContext; shell.IsModal = true; shell.ActiveContext = context; - await shell.ActivateItem(screen); + await shell.ActivateItem(screen, cancellationToken); screen.RunModal(); shell.IsModal = false; shell.ActiveContext = previousContext; @@ -69,62 +70,62 @@ public Task NavigateTo(RxScreen screen, object context = null, IDictionary ShowOverlayDialog(RxScreen screen, object context = null, IDictionary settings = null) + public async Task ShowOverlayDialog(RxScreen screen, object context = null, IDictionary settings = null, CancellationToken cancellationToken = default) { var shell = GetShell(); var previousContext = shell.ActiveContext; shell.Overlay = screen; shell.ActiveContext = context; - await screen.ActivateAsync(); + await ((IActivate)screen).ActivateAsync(cancellationToken); screen.RunModal(); shell.Overlay = null; shell.ActiveContext = previousContext; return screen.Result; } - public async Task ShowMessage(string title, string message, string acceptText = "Ok", bool hideCancel = false) + public async Task ShowMessage(string title, string message, string acceptText = "Ok", bool hideCancel = false, CancellationToken cancellationToken = default) { var messageBox = new MessageBoxViewModel(title, message, acceptText, hideCancel); - var result = await ShowOverlayDialog(messageBox); + var result = await ShowOverlayDialog(messageBox, cancellationToken: cancellationToken); return result ?? false; } - public Task ShowYesNoCancelDialog(string title, string message, string question, string yesText, string noText) + public Task ShowYesNoCancelDialog(string title, string message, string question, string yesText, string noText, CancellationToken cancellationToken = default) { var messageBox = new YesNoCancelViewModel(title, message, question, yesText, noText); - return ShowOverlayDialog(messageBox); + return ShowOverlayDialog(messageBox, cancellationToken: cancellationToken); } - public async Task ShowYesNoDialog(string title, string message, string question, string yesText, string noText) + public async Task ShowYesNoDialog(string title, string message, string question, string yesText, string noText, CancellationToken cancellationToken = default) { var messageBox = new YesNoCancelViewModel(title, message, question, yesText, noText) { ShowCancelButton = false }; - var result = await ShowOverlayDialog(messageBox); + var result = await ShowOverlayDialog(messageBox, cancellationToken: cancellationToken); return result.Value; } - public async Task ShowSliderDialog(SliderDialogViewModel viewModel) + public async Task ShowSliderDialog(SliderDialogViewModel viewModel, CancellationToken cancellationToken = default) { - var result = await ShowOverlayDialog(viewModel); + var result = await ShowOverlayDialog(viewModel, cancellationToken: cancellationToken); return result ?? false; } - public async Task ShowTextBoxDialog(TextBoxDialogViewModel viewModel) + public async Task ShowTextBoxDialog(TextBoxDialogViewModel viewModel, CancellationToken cancellationToken = default) { - var result = await ShowOverlayDialog(viewModel); + var result = await ShowOverlayDialog(viewModel, cancellationToken: cancellationToken); return result ?? false; } - public async Task ShowActionReport(ReportCard reportcard, string title, string errorsMessage = "", string warningsMessage = "") + public async Task ShowActionReport(ReportCard reportcard, string title, string errorsMessage = "", string warningsMessage = "", CancellationToken cancellationToken = default) { var messageBox = reportCardViewModelFactory(reportcard); messageBox.Title = title; messageBox.ErrorsMessage = errorsMessage; messageBox.WarningsMessage = warningsMessage; - var result = await ShowOverlayDialog(messageBox); + var result = await ShowOverlayDialog(messageBox, cancellationToken: cancellationToken); return result ?? false; } diff --git a/src/ServiceControl.Config/UI/AdvancedOptions/ServiceControlAdvancedViewModel.cs b/src/ServiceControl.Config/UI/AdvancedOptions/ServiceControlAdvancedViewModel.cs index d83e9edb0a..638fad9e38 100644 --- a/src/ServiceControl.Config/UI/AdvancedOptions/ServiceControlAdvancedViewModel.cs +++ b/src/ServiceControl.Config/UI/AdvancedOptions/ServiceControlAdvancedViewModel.cs @@ -140,7 +140,7 @@ public bool AllowStop public string ForcedUpgradeBackupLocation => ServiceControlInstance.DatabaseBackupPath; - public Task HandleAsync(PostRefreshInstances message, CancellationToken cancellationToken) + public Task HandleAsync(PostRefreshInstances message, CancellationToken cancellationToken = default) { NotifyOfPropertyChange("AllowStop"); NotifyOfPropertyChange("IsRunning"); @@ -149,7 +149,7 @@ public Task HandleAsync(PostRefreshInstances message, CancellationToken cancella return Task.CompletedTask; } - public async Task StartService(IProgressObject progress, bool maintenanceMode) + public async Task StartService(IProgressObject progress, bool maintenanceMode, CancellationToken cancellationToken = default) { var disposeProgress = progress == null; var result = false; @@ -170,7 +170,7 @@ await Task.Run(() => } result = ServiceControlInstance.TryStartService(); - }); + }, cancellationToken); return result; } @@ -183,7 +183,7 @@ await Task.Run(() => } } - public async Task StopService(IProgressObject progress = null) + public async Task StopService(IProgressObject progress = null, CancellationToken cancellationToken = default) { var disposeProgress = progress == null; var result = false; @@ -200,7 +200,7 @@ await Task.Run(() => { ServiceControlInstance.DisableMaintenanceMode(); } - }); + }, cancellationToken); return result; } diff --git a/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs b/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs index ed627be581..04dee4fccd 100644 --- a/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs +++ b/src/ServiceControl.Config/UI/FeedBack/FeedBackViewModel.cs @@ -1,5 +1,7 @@ namespace ServiceControl.Config.UI.FeedBack { + using System; + using System.Threading; using System.Threading.Tasks; using System.Windows.Input; using Framework; @@ -15,7 +17,7 @@ public FeedBackViewModel(RaygunFeedback raygunFeedBack) feedBack = raygunFeedBack; validationTemplate = new ValidationTemplate(this); Cancel = Command.Create(async () => await TryCloseAsync(false)); - SendFeedBack = Command.Create(async () => await Send()); + SendFeedBack = Command.Create(async () => await Send(CancellationToken.None)); } public string EmailAddress { get; set; } @@ -30,7 +32,7 @@ public FeedBackViewModel(RaygunFeedback raygunFeedBack) public bool SubmitAttempted { get; set; } - async Task Send() + async Task Send(CancellationToken cancellationToken) { SubmitAttempted = true; if (!validationTemplate.Validate()) @@ -42,9 +44,13 @@ async Task Send() try { - await feedBack.SendFeedBack(EmailAddress, Message, IncludeSystemInfo); + await feedBack.SendFeedBack(EmailAddress, Message, IncludeSystemInfo, cancellationToken); Success = true; } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } catch { Success = false; diff --git a/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs b/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs index 384118f339..d6d3ada53b 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/MonitoringAddAttachment.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.UI.InstanceAdd { using System; + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -40,7 +41,7 @@ bool IsInProgress() return viewModel != null && !viewModel.InProgress; } - async Task Add() + async Task Add(CancellationToken cancellationToken) { viewModel.SubmitAttempted = true; if (!viewModel.ValidationTemplate.Validate()) @@ -71,7 +72,7 @@ async Task Add() ServiceAccountPwd = viewModel.Password }; - if (!await commandChecks.ValidateNewInstance(instanceMetadata)) + if (!await commandChecks.ValidateNewInstance([instanceMetadata], cancellationToken)) { viewModel.InProgress = false; return; @@ -79,11 +80,11 @@ async Task Add() using (var progress = viewModel.GetProgressObject("ADDING INSTANCE")) { - var reportCard = await Task.Run(() => installer.Add(instanceMetadata, progress, PromptToProceed)); + var reportCard = await Task.Run(() => installer.Add(instanceMetadata, progress, PromptToProceed, cancellationToken), cancellationToken); if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES ADDING INSTANCE", "Could not add new instance because of the following errors:", "There were some warnings while adding the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES ADDING INSTANCE", "Could not add new instance because of the following errors:", "There were some warnings while adding the instance:", cancellationToken); return; } @@ -95,14 +96,14 @@ async Task Add() await viewModel.TryCloseAsync(true); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); } - async Task PromptToProceed(PathInfo pathInfo) + async Task PromptToProceed(PathInfo pathInfo, CancellationToken cancellationToken) { var result = false; - await Execute.OnUIThreadAsync(async () => { result = await windowManager.ShowYesNoDialog("ADDING INSTANCE QUESTION - DIRECTORY NOT EMPTY", $"The directory specified as the {pathInfo.Name} is not empty.", $"Are you sure you want to use '{pathInfo.Path}' ?", "Yes use it", "No I want to change it"); }); + await Execute.OnUIThreadAsync(async () => { result = await windowManager.ShowYesNoDialog("ADDING INSTANCE QUESTION - DIRECTORY NOT EMPTY", $"The directory specified as the {pathInfo.Name} is not empty.", $"Are you sure you want to use '{pathInfo.Path}' ?", "Yes use it", "No I want to change it", cancellationToken); }); return result; } diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs index 5c9863dd03..8cf5c88352 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.UI.InstanceAdd { using System; + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -38,7 +39,7 @@ protected override void OnAttach() bool IsInProgress() => viewModel != null && !viewModel.InProgress; - async Task Add() + async Task Add(CancellationToken cancellationToken) { viewModel.SubmitAttempted = true; if (!viewModel.ValidationTemplate.Validate()) @@ -104,7 +105,7 @@ async Task Add() auditNewInstance.EnableFullTextSearchOnBodies = viewModel.ServiceControlAudit.EnableFullTextSearchOnBodies.Value; } - if (!await commandChecks.ValidateNewInstance(serviceControlNewInstance, auditNewInstance)) + if (!await commandChecks.ValidateNewInstance([serviceControlNewInstance, auditNewInstance], cancellationToken)) { viewModel.InProgress = false; return; @@ -119,7 +120,7 @@ async Task Add() { using (var progress = viewModel.GetProgressObject("ADDING INSTANCE")) { - var installationCancelled = await InstallInstance(serviceControlNewInstance, progress); + var installationCancelled = await InstallInstance(serviceControlNewInstance, progress, cancellationToken); if (installationCancelled) { return; @@ -131,7 +132,7 @@ async Task Add() { using (var progress = viewModel.GetProgressObject("ADDING AUDIT INSTANCE")) { - var installationCancelled = await InstallInstance(auditNewInstance, progress); + var installationCancelled = await InstallInstance(auditNewInstance, progress, cancellationToken); if (installationCancelled) { return; @@ -141,16 +142,16 @@ async Task Add() await viewModel.TryCloseAsync(true); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); } - async Task InstallInstance(ServiceControlNewInstance instanceData, IProgressObject progress) + async Task InstallInstance(ServiceControlNewInstance instanceData, IProgressObject progress, CancellationToken cancellationToken) { - var reportCard = await Task.Run(() => serviceControlInstaller.Add(instanceData, progress, PromptToProceed)); + var reportCard = await Task.Run(() => serviceControlInstaller.Add(instanceData, progress, PromptToProceed, cancellationToken), cancellationToken); if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES ADDING INSTANCE", "Could not add new instance because of the following errors:", "There were some warnings while adding the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES ADDING INSTANCE", "Could not add new instance because of the following errors:", "There were some warnings while adding the instance:", cancellationToken); return true; } @@ -162,13 +163,13 @@ async Task InstallInstance(ServiceControlNewInstance instanceData, IProgre return false; } - async Task InstallInstance(ServiceControlAuditNewInstance instanceData, IProgressObject progress) + async Task InstallInstance(ServiceControlAuditNewInstance instanceData, IProgressObject progress, CancellationToken cancellationToken) { - var reportCard = await Task.Run(() => serviceControlAuditInstaller.Add(instanceData, progress, PromptToProceed)); + var reportCard = await Task.Run(() => serviceControlAuditInstaller.Add(instanceData, progress, PromptToProceed, cancellationToken), cancellationToken); if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES ADDING INSTANCE", "Could not add new instance because of the following errors:", "There were some warnings while adding the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES ADDING INSTANCE", "Could not add new instance because of the following errors:", "There were some warnings while adding the instance:", cancellationToken); return true; } @@ -180,11 +181,11 @@ async Task InstallInstance(ServiceControlAuditNewInstance instanceData, IP return false; } - async Task PromptToProceed(PathInfo pathInfo) + async Task PromptToProceed(PathInfo pathInfo, CancellationToken cancellationToken) { var result = false; - await Execute.OnUIThreadAsync(async () => { result = await windowManager.ShowYesNoDialog("ADDING INSTANCE QUESTION - DIRECTORY NOT EMPTY", $"The directory specified as the {pathInfo.Name} is not empty.", $"Are you sure you want to use '{pathInfo.Path}' ?", "Yes use it", "No I want to change it"); }); + await Execute.OnUIThreadAsync(async () => { result = await windowManager.ShowYesNoDialog("ADDING INSTANCE QUESTION - DIRECTORY NOT EMPTY", $"The directory specified as the {pathInfo.Name} is not empty.", $"Are you sure you want to use '{pathInfo.Path}' ?", "Yes use it", "No I want to change it", cancellationToken); }); return result; } diff --git a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs index 4a1c9dd14d..8264f7b54d 100644 --- a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs +++ b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs @@ -362,7 +362,7 @@ public bool AllowStop public bool Exists() => ServiceInstance.Service.Exists(); - public Task HandleAsync(PostRefreshInstances message, CancellationToken cancellationToken) + public Task HandleAsync(PostRefreshInstances message, CancellationToken cancellationToken = default) { UpdateServiceProperties(); @@ -383,7 +383,7 @@ public Task HandleAsync(PostRefreshInstances message, CancellationToken cancella return Task.CompletedTask; } - public async Task StartService(IProgressObject progress = null) + public async Task StartService(IProgressObject progress = null, CancellationToken cancellationToken = default) { var disposeProgress = progress == null; var result = false; @@ -414,7 +414,7 @@ public async Task StartService(IProgressObject progress = null) } } - public async Task StopService(IProgressObject progress = null) + public async Task StopService(IProgressObject progress = null, CancellationToken cancellationToken = default) { var disposeProgress = progress == null; var result = false; @@ -431,7 +431,7 @@ await Task.Run(() => { ServiceControlInstance.DisableMaintenanceMode(); } - }); + }, cancellationToken); UpdateServiceProperties(); diff --git a/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditAttachment.cs b/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditAttachment.cs index 86d38420de..4c0d22e74d 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/MonitoringEditAttachment.cs @@ -2,6 +2,7 @@ namespace ServiceControl.Config.UI.InstanceEdit { using System; using System.ServiceProcess; + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -28,7 +29,7 @@ protected override void OnAttach() viewModel.Cancel = Command.Create(async () => { await viewModel.TryCloseAsync(false); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), CancellationToken.None); }, IsInProgress); } @@ -37,7 +38,7 @@ bool IsInProgress() return viewModel != null && !viewModel.InProgress; } - async Task Save() + async Task Save(CancellationToken cancellationToken) { viewModel.SubmitAttempted = true; if (!viewModel.ValidationTemplate.Validate()) @@ -53,7 +54,7 @@ async Task Save() if (instance.Service.Status == ServiceControllerStatus.Running) { var shouldProceed = await windowManager.ShowMessage("STOP INSTANCE AND MODIFY", - $"{instance.Name} needs to be stopped in order to modify the settings. Do you want to proceed."); + $"{instance.Name} needs to be stopped in order to modify the settings. Do you want to proceed.", cancellationToken: cancellationToken); if (!shouldProceed) { return; @@ -81,7 +82,7 @@ async Task Save() if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES MODIFYING INSTANCE", "Could not modify instance because of the following errors:", "There were some warnings while modifying the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES MODIFYING INSTANCE", "Could not modify instance because of the following errors:", "There were some warnings while modifying the instance:", cancellationToken: cancellationToken); return; } @@ -90,7 +91,7 @@ async Task Save() await viewModel.TryCloseAsync(true); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); } readonly IServiceControlWindowManager windowManager; diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditAttachment.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditAttachment.cs index b7b876dbff..3a3c4f8e97 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlAuditEditAttachment.cs @@ -2,6 +2,7 @@ namespace ServiceControl.Config.UI.InstanceEdit { using System; using System.ServiceProcess; + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -28,7 +29,7 @@ protected override void OnAttach() viewModel.Cancel = Command.Create(async () => { await viewModel.TryCloseAsync(false); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), CancellationToken.None); }, IsInProgress); } @@ -37,7 +38,7 @@ bool IsInProgress() return viewModel != null && !viewModel.InProgress; } - async Task Save() + async Task Save(CancellationToken cancellationToken) { viewModel.SubmitAttempted = true; if (!viewModel.ValidationTemplate.Validate()) @@ -52,7 +53,7 @@ async Task Save() if (instance.Service.Status == ServiceControllerStatus.Running) { var shouldProceed = await windowManager.ShowMessage("STOP INSTANCE AND MODIFY", - $"{instance.Name} needs to be stopped in order to modify the settings. Do you want to proceed."); + $"{instance.Name} needs to be stopped in order to modify the settings. Do you want to proceed.", cancellationToken: cancellationToken); if (!shouldProceed) { return; @@ -86,7 +87,7 @@ async Task Save() if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES MODIFYING INSTANCE", "Could not modify instance because of the following errors:", "There were some warnings while modifying the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES MODIFYING INSTANCE", "Could not modify instance because of the following errors:", "There were some warnings while modifying the instance:", cancellationToken: cancellationToken); return; } @@ -95,7 +96,7 @@ async Task Save() await viewModel.TryCloseAsync(true); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); } readonly IServiceControlWindowManager windowManager; diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs index f978f2f82c..3ae699c8fd 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs @@ -2,6 +2,7 @@ namespace ServiceControl.Config.UI.InstanceEdit { using System; using System.ServiceProcess; + using System.Threading; using System.Threading.Tasks; using Caliburn.Micro; using Events; @@ -28,7 +29,7 @@ protected override void OnAttach() viewModel.Cancel = Command.Create(async () => { await viewModel.TryCloseAsync(false); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), CancellationToken.None); }, IsInProgress); } @@ -37,7 +38,7 @@ bool IsInProgress() return viewModel != null && !viewModel.InProgress; } - async Task Save() + async Task Save(CancellationToken cancellationToken) { viewModel.SubmitAttempted = true; if (!viewModel.ValidationTemplate.Validate()) @@ -52,7 +53,7 @@ async Task Save() if (instance.Service.Status == ServiceControllerStatus.Running) { var shouldProceed = await windowManager.ShowMessage("STOP INSTANCE AND MODIFY", - $"{instance.Name} needs to be stopped in order to modify the settings. Do you want to proceed."); + $"{instance.Name} needs to be stopped in order to modify the settings. Do you want to proceed.", cancellationToken: cancellationToken); if (!shouldProceed) { return; @@ -88,7 +89,7 @@ async Task Save() if (reportCard.HasErrors || reportCard.HasWarnings) { - await windowManager.ShowActionReport(reportCard, "ISSUES MODIFYING INSTANCE", "Could not modify instance because of the following errors:", "There were some warnings while modifying the instance:"); + await windowManager.ShowActionReport(reportCard, "ISSUES MODIFYING INSTANCE", "Could not modify instance because of the following errors:", "There were some warnings while modifying the instance:", cancellationToken: cancellationToken); return; } @@ -97,7 +98,7 @@ async Task Save() await viewModel.TryCloseAsync(true); - await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances(), cancellationToken); } readonly IServiceControlWindowManager windowManager; diff --git a/src/ServiceControl.Config/UI/License/LicenseViewModel.cs b/src/ServiceControl.Config/UI/License/LicenseViewModel.cs index efcfd2d27d..8eecdde9d1 100644 --- a/src/ServiceControl.Config/UI/License/LicenseViewModel.cs +++ b/src/ServiceControl.Config/UI/License/LicenseViewModel.cs @@ -2,6 +2,7 @@ { using System.Collections.Generic; using System.Linq; + using System.Threading; using System.Threading.Tasks; using System.Windows.Input; using Caliburn.Micro; @@ -29,7 +30,7 @@ class LicenseViewModel : RxScreen public string ExtendLicenseUrl { get; set; } - protected override Task OnActivate() + protected override Task OnActivate(CancellationToken cancellationToken = default) { RefreshLicenseInfo(); return Task.CompletedTask; @@ -46,7 +47,9 @@ void RefreshLicenseInfo() ExtendLicenseUrl = $"https://particular.net/license/nservicebus?t={(license.IsEvaluationLicense ? 0 : 1)}&p=servicecontrol"; } +#pragma warning disable PS0018 // Bound to AwaitableSelectPathCommand's Func, which the tokenless ICommand.Execute drives async Task OpenLicenseFile(string path) +#pragma warning restore PS0018 { if (LicenseManager.TryImportLicense(path, out var importError)) { diff --git a/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs b/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs index 0313b9de8e..bb89720cd6 100644 --- a/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs +++ b/src/ServiceControl.Config/UI/ListInstances/ListInstancesViewModel.cs @@ -67,7 +67,7 @@ public string ConfigurationErrorMessage [AlsoNotifyFor(nameof(OrderedInstances), nameof(HasConfigurationErrors), nameof(ConfigurationErrorMessage))] IList Instances { get; } - public Task HandleAsync(LicenseUpdated licenseUpdatedEvent, CancellationToken cancellationToken) + public Task HandleAsync(LicenseUpdated licenseUpdatedEvent, CancellationToken cancellationToken = default) { // on license change inform each instance to refresh the license (1.23.0 and below don't support this) foreach (var instance in Instances) @@ -105,13 +105,13 @@ public Task HandleAsync(LicenseUpdated licenseUpdatedEvent, CancellationToken ca /// before the PostRefreshInstances handlers do all their rebinding. That way, deleting an instance /// in PowerShell won't cause an error from a deleted instance viewmodel trying to refresh itself. /// - public async Task HandleAsync(RefreshInstances message, CancellationToken cancellationToken) + public async Task HandleAsync(RefreshInstances message, CancellationToken cancellationToken = default) { AddAndRemoveInstances(); await EventAggregator.PublishOnUIThreadAsync(new PostRefreshInstances(), cancellationToken); } - public async Task HandleAsync(ResetInstances message, CancellationToken cancellationToken) + public async Task HandleAsync(ResetInstances message, CancellationToken cancellationToken = default) { foreach (var instance in Instances) { diff --git a/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs b/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs index bfb921fa8e..40f7cb82f6 100644 --- a/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs +++ b/src/ServiceControl.Config/UI/MessageBox/ExceptionMessageBox.xaml.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.UI.MessageBox { using System; + using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -95,7 +96,9 @@ void CopyClick(object sender, RoutedEventArgs e) Clipboard.SetText(ErrorDetails.Text); } +#pragma warning disable PS0018 // Bound to AwaitableDelegateCommand's Func, which the tokenless ICommand.Execute drives async Task CallReportClick(object sender) +#pragma warning restore PS0018 { ProgressTitle = "Processing"; ProgressMessage = "Sending Exception Details..."; diff --git a/src/ServiceControl.Config/UI/Shell/FeedBackAttachment.cs b/src/ServiceControl.Config/UI/Shell/FeedBackAttachment.cs index 128ba1d451..a0616a2f56 100644 --- a/src/ServiceControl.Config/UI/Shell/FeedBackAttachment.cs +++ b/src/ServiceControl.Config/UI/Shell/FeedBackAttachment.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Config.UI.Shell { using System; + using System.Threading; using System.Threading.Tasks; using FeedBack; using Framework; @@ -23,10 +24,10 @@ public FeedBackAttachment( protected override void OnAttach() { - viewModel.OpenFeedBack = Command.Create(async () => await FeedBack()); + viewModel.OpenFeedBack = Command.Create(async () => await FeedBack(CancellationToken.None)); } - async Task FeedBack() + async Task FeedBack(CancellationToken cancellationToken) { if (raygunFeedBack.Enabled) { diff --git a/src/ServiceControl.Config/UI/Shell/LicenseStatusManager.cs b/src/ServiceControl.Config/UI/Shell/LicenseStatusManager.cs index 241163baac..f73dc3e7de 100644 --- a/src/ServiceControl.Config/UI/Shell/LicenseStatusManager.cs +++ b/src/ServiceControl.Config/UI/Shell/LicenseStatusManager.cs @@ -37,13 +37,13 @@ public bool ShowPopup public bool IsSerious { get; private set; } public bool HasFocus { get; private set; } - public Task HandleAsync(FocusChanged message, CancellationToken cancellationToken) + public Task HandleAsync(FocusChanged message, CancellationToken cancellationToken = default) { HasFocus = message.HasFocus; return Task.CompletedTask; } - public Task HandleAsync(LicenseUpdated message, CancellationToken cancellationToken) + public Task HandleAsync(LicenseUpdated message, CancellationToken cancellationToken = default) { RefreshStatus(false); return Task.CompletedTask; diff --git a/src/ServiceControl.Config/UI/Shell/ShellViewModel.cs b/src/ServiceControl.Config/UI/Shell/ShellViewModel.cs index ea9328d98f..fb50e3a731 100644 --- a/src/ServiceControl.Config/UI/Shell/ShellViewModel.cs +++ b/src/ServiceControl.Config/UI/Shell/ShellViewModel.cs @@ -86,20 +86,20 @@ IEventAggregator eventAggregator public string AvailableUpgradeReleaseLink { get; set; } - public Task HandleAsync(PostRefreshInstances message, CancellationToken cancellationToken) => RefreshInstances(); + public Task HandleAsync(PostRefreshInstances message, CancellationToken cancellationToken = default) => RefreshInstances(cancellationToken); - public Task HandleAsync(ResetInstances message, CancellationToken cancellationToken) => RefreshInstances(); + public Task HandleAsync(ResetInstances message, CancellationToken cancellationToken = default) => RefreshInstances(cancellationToken); - protected override Task OnInitialize() => RefreshInstances(); + protected override Task OnInitialize(CancellationToken cancellationToken = default) => RefreshInstances(cancellationToken); - protected override async Task OnActivate() + protected override async Task OnActivate(CancellationToken cancellationToken = default) { - await base.OnActivate(); + await base.OnActivate(cancellationToken); BeginCheckForUpdates(); } - public async Task RefreshInstances() + public async Task RefreshInstances(CancellationToken cancellationToken = default) { HasInstances = InstanceFinder.AllInstances().Any(); @@ -107,11 +107,11 @@ public async Task RefreshInstances() { if (HasInstances) { - await ActivateItem(listInstances); + await ActivateItem(listInstances, cancellationToken); } else { - await ActivateItem(noInstances); + await ActivateItem(noInstances, cancellationToken); } } } @@ -125,16 +125,16 @@ void BeginCheckForUpdates() return; } - updateCheckTask = CheckForUpdates(); + updateCheckTask = CheckForUpdates(CancellationToken.None); NotifyOfPropertyChange(nameof(IsCheckingForUpdate)); } - async Task CheckForUpdates() + async Task CheckForUpdates(CancellationToken cancellationToken) { try { - var availableUpgradeRelease = await VersionCheckerHelper.GetLatestRelease(AppVersion); + var availableUpgradeRelease = await VersionCheckerHelper.GetLatestRelease(AppVersion, cancellationToken); if (availableUpgradeRelease.Version == AppVersion) { @@ -147,6 +147,10 @@ async Task CheckForUpdates() UpdateAvailable = true; } } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } catch { UpdateAvailable = false; diff --git a/src/ServiceControl.Config/UI/Shell/VersionCheckerHelper.cs b/src/ServiceControl.Config/UI/Shell/VersionCheckerHelper.cs index e91190b5e3..d1bf7786d3 100644 --- a/src/ServiceControl.Config/UI/Shell/VersionCheckerHelper.cs +++ b/src/ServiceControl.Config/UI/Shell/VersionCheckerHelper.cs @@ -8,14 +8,15 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json.Serialization; + using System.Threading; using System.Threading.Tasks; using NuGet.Versioning; public static class VersionCheckerHelper { - public static async Task GetLatestRelease(SemanticVersion currentVersion) + public static async Task GetLatestRelease(SemanticVersion currentVersion, CancellationToken cancellationToken = default) { - List releases = await GetVersionInformation(); + List releases = await GetVersionInformation(cancellationToken); if (releases != null) { @@ -31,11 +32,15 @@ public static async Task GetLatestRelease(SemanticVersion currentVersio return new Release(currentVersion); } - static async Task> GetVersionInformation() + static async Task> GetVersionInformation(CancellationToken cancellationToken) { try { - return await httpClient.GetFromJsonAsync>("https://s3.us-east-1.amazonaws.com/platformupdate.particular.net/servicecontrol.txt"); + return await httpClient.GetFromJsonAsync>("https://s3.us-east-1.amazonaws.com/platformupdate.particular.net/servicecontrol.txt", cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; } catch { diff --git a/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceAttachment.cs b/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceAttachment.cs index 49cd2b357d..80e0b464de 100644 --- a/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceAttachment.cs +++ b/src/ServiceControl.Config/UI/Upgrades/AddNewAuditInstanceAttachment.cs @@ -1,5 +1,6 @@ namespace ServiceControl.Config.UI.Upgrades { + using System.Threading; using System.Threading.Tasks; using Framework; using ReactiveUI; @@ -28,7 +29,7 @@ protected override void OnAttach() viewModel.Continue = ReactiveCommand.CreateFromTask(Continue); } - async Task Continue() + async Task Continue(CancellationToken cancellationToken) { viewModel.SubmitAttempted = true; diff --git a/src/ServiceControl.Management.PowerShell/.editorconfig b/src/ServiceControl.Management.PowerShell/.editorconfig index 2815635efb..c0448b0dda 100644 --- a/src/ServiceControl.Management.PowerShell/.editorconfig +++ b/src/ServiceControl.Management.PowerShell/.editorconfig @@ -1,6 +1 @@ [*.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.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs index c531667c82..768041419f 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/AuditInstances/NewServiceControlAuditInstance.cs @@ -2,6 +2,7 @@ { using System; using System.Management.Automation; + using System.Threading; using System.Threading.Tasks; using ServiceControlInstaller.Engine.Instances; using ServiceControlInstaller.Engine.Unattended; @@ -172,7 +173,7 @@ protected override void ProcessRecord() { return; } - if (!checks.ValidateNewInstance(newAuditInstance).GetAwaiter().GetResult()) + if (!checks.ValidateNewInstance([newAuditInstance]).GetAwaiter().GetResult()) { return; } @@ -201,7 +202,7 @@ protected override void ProcessRecord() } } - Task PromptToProceed(PathInfo pathInfo) + Task PromptToProceed(PathInfo pathInfo, CancellationToken cancellationToken) { if (!pathInfo.CheckIfEmpty) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs index 8f90c7e732..315a8bb828 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/MonitoringInstances/NewMonitoringInstance.cs @@ -2,6 +2,7 @@ namespace ServiceControl.Management.PowerShell { using System; using System.Management.Automation; + using System.Threading; using System.Threading.Tasks; using Cmdlets.Instances; using ServiceControlInstaller.Engine.Instances; @@ -142,7 +143,7 @@ protected override void ProcessRecord() { return; } - if (!checks.ValidateNewInstance(monitoringNewInstance).GetAwaiter().GetResult()) + if (!checks.ValidateNewInstance([monitoringNewInstance]).GetAwaiter().GetResult()) { return; } @@ -171,7 +172,7 @@ protected override void ProcessRecord() } } - Task PromptToProceed(PathInfo pathInfo) + Task PromptToProceed(PathInfo pathInfo, CancellationToken cancellationToken) { if (!pathInfo.CheckIfEmpty) { diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs index 96e905e064..eb2e792fcf 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs @@ -4,6 +4,7 @@ namespace ServiceControl.Management.PowerShell using System.IO; using System.Linq; using System.Management.Automation; + using System.Threading; using System.Threading.Tasks; using ServiceControlInstaller.Engine.Instances; using ServiceControlInstaller.Engine.Unattended; @@ -188,7 +189,7 @@ protected override void ProcessRecord() { return; } - if (!checks.ValidateNewInstance(details).GetAwaiter().GetResult()) + if (!checks.ValidateNewInstance([details]).GetAwaiter().GetResult()) { return; } @@ -228,7 +229,7 @@ protected override void ProcessRecord() } } - Task PromptToProceed(PathInfo pathInfo) + Task PromptToProceed(PathInfo pathInfo, CancellationToken cancellationToken) { if (!pathInfo.CheckIfEmpty) { diff --git a/src/ServiceControl.Management.PowerShell/Validation/PowerShellCommandChecks.cs b/src/ServiceControl.Management.PowerShell/Validation/PowerShellCommandChecks.cs index 9d9ffd27bc..ca81261a14 100644 --- a/src/ServiceControl.Management.PowerShell/Validation/PowerShellCommandChecks.cs +++ b/src/ServiceControl.Management.PowerShell/Validation/PowerShellCommandChecks.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Management.Automation; using System.Text; + using System.Threading; using System.Threading.Tasks; using ServiceControlInstaller.Engine; using ServiceControlInstaller.Engine.Configuration.ServiceControl; @@ -28,21 +29,21 @@ void Terminate(string message, string errorId, ErrorCategory category) cmdlet.ThrowTerminatingError(errorRecord); } - protected override Task NotifyForDeprecatedMessageTransport(TransportInfo transport) + protected override Task NotifyForDeprecatedMessageTransport(TransportInfo transport, CancellationToken cancellationToken = default) { var terminateMsg = $"The message transport '{transport.DisplayName}' is not available in this version of ServiceControl, and this instance cannot be upgraded."; Terminate(terminateMsg, "Install Error", ErrorCategory.InvalidOperation); return Task.CompletedTask; } - protected override Task NotifyForIncompatibleStorageEngine(IServiceControlBaseInstance baseInstance) + protected override Task NotifyForIncompatibleStorageEngine(IServiceControlBaseInstance baseInstance, CancellationToken cancellationToken = default) { var msg = $"The storage format has changed and the {baseInstance.PersistenceManifest.DisplayName} storage engine is no longer available. Upgrading requires a side-by-side deployment of both versions. Migration guidance is available in the version 4 to 5 upgrade guidance at {UpgradeGuide4to5Url}"; Terminate(msg, "Install Error", ErrorCategory.InvalidOperation); return Task.CompletedTask; } - protected override Task NotifyForIncompatibleUpgradeVersion(UpgradeInfo upgradeInfo) + protected override Task NotifyForIncompatibleUpgradeVersion(UpgradeInfo upgradeInfo, CancellationToken cancellationToken = default) { var nextVersion = upgradeInfo.UpgradePath[0]; var b = new StringBuilder(); @@ -57,19 +58,19 @@ protected override Task NotifyForIncompatibleUpgradeVersion(UpgradeInfo upgradeI return Task.CompletedTask; } - protected override Task NotifyError(string title, string message) + protected override Task NotifyError(string title, string message, CancellationToken cancellationToken = default) { Terminate(message, title, ErrorCategory.InvalidOperation); return Task.CompletedTask; } - protected override Task NotifyForMissingSystemPrerequisites(string missingPrereqsMessage) + protected override Task NotifyForMissingSystemPrerequisites(string missingPrereqsMessage, CancellationToken cancellationToken = default) { Terminate(missingPrereqsMessage, "Missing Prerequisites", ErrorCategory.NotInstalled); return Task.CompletedTask; } - protected override Task PromptForRabbitMqCheck(bool isUpgrade) + protected override Task PromptForRabbitMqCheck(bool isUpgrade, CancellationToken cancellationToken = default) { if (!acknowledgements.Any(ack => ack.Equals(AcknowledgementValues.RabbitMQBrokerVersion310, StringComparison.OrdinalIgnoreCase))) { @@ -90,13 +91,13 @@ protected override Task PromptForRabbitMqCheck(bool isUpgrade) return Task.FromResult(true); } - protected override Task PromptToStopRunningInstance(BaseService instance) + protected override Task PromptToStopRunningInstance(BaseService instance, CancellationToken cancellationToken = default) { // PowerShell assumes you always want to stop the service if it's running return Task.FromResult(true); } - protected override Task PromptToContinueWithForcedUpgrade() + protected override Task PromptToContinueWithForcedUpgrade(CancellationToken cancellationToken = default) { // In PowerShell, you passed the -Force parameter to get here in the first place return Task.FromResult(true); diff --git a/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs b/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs index 97927c0bcf..d45f8f1866 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/RunEngineTasksExplicitly.cs @@ -72,13 +72,13 @@ public async Task CreateInstanceMSMQ() // constructer of ServiceControlInstanceMetadata extracts version from zip details.Version = Constants.CurrentVersion; - await details.Validate(s => Task.FromResult(false)).ConfigureAwait(false); + await details.Validate((s, token) => Task.FromResult(false)).ConfigureAwait(false); if (details.ReportCard.HasErrors) { throw new Exception($"Validation errors: {string.Join("\r\n", details.ReportCard.Errors)}"); } - Assert.DoesNotThrowAsync(() => installer.Add(details, s => Task.FromResult(false))); + Assert.DoesNotThrowAsync(() => installer.Add(details, (s, token) => Task.FromResult(false))); } [Test] diff --git a/src/ServiceControlInstaller.Engine/.editorconfig b/src/ServiceControlInstaller.Engine/.editorconfig index e3bf403607..c0448b0dda 100644 --- a/src/ServiceControlInstaller.Engine/.editorconfig +++ b/src/ServiceControlInstaller.Engine/.editorconfig @@ -1,5 +1 @@ -[*.cs] - -# may be enabled in future -dotnet_diagnostic.PS0013.severity = none # A Func used as a method parameter with a Task, ValueTask, or ValueTask return type argument should have at least one CancellationToken parameter type argument unless it has a parameter type argument implementing ICancellableContext -dotnet_diagnostic.PS0018.severity = none # A task-returning method should have a CancellationToken parameter unless it has a parameter implementing ICancellableContext +[*.cs] diff --git a/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs b/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs index 09a91d848c..acc834a920 100644 --- a/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/MonitoringInstance.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.AccessControl; using System.Security.Principal; + using System.Threading; using System.Threading.Tasks; using Accounts; using Configuration; @@ -136,11 +137,11 @@ TransportInfo DetermineTransportPackage() return transport ?? throw new Exception($"{SettingsList.TransportType.Name} value of '{transportAppSetting}' in app.config is invalid."); } - public async Task ValidateChanges() + public async Task ValidateChanges(CancellationToken cancellationToken = default) { try { - await new PathsValidator(this).RunValidation(false).ConfigureAwait(false); + await new PathsValidator(this).RunValidation(false, cancellationToken).ConfigureAwait(false); } catch (EngineValidationException ex) { diff --git a/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs b/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs index 3339de8823..6b629e8e9e 100644 --- a/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/MonitoringNewInstance.cs @@ -5,6 +5,7 @@ using System.IO; using System.Security.AccessControl; using System.Security.Principal; + using System.Threading; using System.Threading.Tasks; using Accounts; using Configuration.Monitoring; @@ -142,7 +143,7 @@ public void SetupInstance() } } - public async Task Validate(Func> promptToProceed) + public async Task Validate(Func> promptToProceed, CancellationToken cancellationToken = default) { if (TransportPackage.ZipName.Equals("MSMQ", StringComparison.OrdinalIgnoreCase)) { @@ -167,7 +168,7 @@ public async Task Validate(Func> promptToProceed) try { - ReportCard.CancelRequested = await new PathsValidator(this).RunValidation(true, promptToProceed).ConfigureAwait(false); + ReportCard.CancelRequested = await new PathsValidator(this).RunValidation(true, promptToProceed, cancellationToken).ConfigureAwait(false); } catch (EngineValidationException ex) { diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs index b0c5916ff1..e2983cb02e 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs @@ -7,6 +7,7 @@ namespace ServiceControlInstaller.Engine.Instances using System.Linq; using System.Security.AccessControl; using System.Security.Principal; + using System.Threading; using System.Threading.Tasks; using Accounts; using Configuration; @@ -300,7 +301,7 @@ protected virtual void ValidateConnectionString() { } - protected virtual Task ValidatePaths() => Task.CompletedTask; + protected virtual Task ValidatePaths(CancellationToken cancellationToken = default) => Task.CompletedTask; protected virtual void ValidateQueueNames() { @@ -360,9 +361,9 @@ public void SetupInstance() } } - public async Task ValidateChanges() + public async Task ValidateChanges(CancellationToken cancellationToken = default) { - await ValidatePaths().ConfigureAwait(false); + await ValidatePaths(cancellationToken).ConfigureAwait(false); ValidateQueueNames(); diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs index 49d7b73ca9..d66df319ee 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs @@ -5,6 +5,7 @@ using System.IO; using System.Security.AccessControl; using System.Security.Principal; + using System.Threading; using System.Threading.Tasks; using System.Xml.Serialization; using Accounts; @@ -219,7 +220,7 @@ public void Save(string path) } } - public async Task Validate(Func> promptToProceed) + public async Task Validate(Func> promptToProceed, CancellationToken cancellationToken = default) { RunValidation(ValidateTransport); RunValidation(ValidatePort); @@ -227,7 +228,7 @@ public async Task Validate(Func> promptToProceed) try { - ReportCard.CancelRequested = await ValidatePaths(promptToProceed).ConfigureAwait(false); + ReportCard.CancelRequested = await ValidatePaths(promptToProceed, cancellationToken).ConfigureAwait(false); } catch (EngineValidationException ex) { @@ -267,9 +268,9 @@ protected virtual void ValidateQueueNames() { } - protected virtual Task ValidatePaths(Func> promptToProceed) + protected virtual Task ValidatePaths(Func> promptToProceed, CancellationToken cancellationToken = default) { - return new PathsValidator(this).RunValidation(true, promptToProceed); + return new PathsValidator(this).RunValidation(true, promptToProceed, cancellationToken); } protected virtual void ValidateMaintenancePort() diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs index 9817315782..34328ebe5d 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs @@ -5,6 +5,7 @@ namespace ServiceControlInstaller.Engine.Instances using System.Configuration; using System.IO; using System.Linq; + using System.Threading; using System.Threading.Tasks; using Configuration; using Configuration.ServiceControl; @@ -96,11 +97,11 @@ protected override void ValidateQueueNames() } } - protected override async Task ValidatePaths() + protected override async Task ValidatePaths(CancellationToken cancellationToken = default) { try { - await new PathsValidator(this).RunValidation(false).ConfigureAwait(false); + await new PathsValidator(this).RunValidation(false, cancellationToken).ConfigureAwait(false); } catch (EngineValidationException ex) { diff --git a/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs b/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs index eb80879cdb..8280fb29a8 100644 --- a/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs +++ b/src/ServiceControlInstaller.Engine/Unattended/UnattendAuditInstaller.cs @@ -2,6 +2,7 @@ { using System; using System.ServiceProcess; + using System.Threading; using System.Threading.Tasks; using FileSystem; using Instances; @@ -18,7 +19,7 @@ public UnattendAuditInstaller(ILogging loggingInstance) public PlatformZipInfo ZipInfo { get; } - public async Task Add(ServiceControlAuditNewInstance details, Func> promptToProceed) + public async Task Add(ServiceControlAuditNewInstance details, Func> promptToProceed, CancellationToken cancellationToken = default) { ZipInfo.ValidateZip(); @@ -27,7 +28,7 @@ public async Task Add(ServiceControlAuditNewInstance details, Func Add(MonitoringNewInstance details, Func> promptToProceed) + public async Task Add(MonitoringNewInstance details, Func> promptToProceed, CancellationToken cancellationToken = default) { ZipInfo.ValidateZip(); @@ -26,7 +27,7 @@ public async Task Add(MonitoringNewInstance details, Func Update(MonitoringInstance instance, bool startService) + internal async Task Update(MonitoringInstance instance, bool startService, CancellationToken cancellationToken = default) { instance.ReportCard = new ReportCard(); - await instance.ValidateChanges().ConfigureAwait(false); + await instance.ValidateChanges(cancellationToken).ConfigureAwait(false); if (instance.ReportCard.HasErrors) { foreach (var error in instance.ReportCard.Errors) diff --git a/src/ServiceControlInstaller.Engine/Unattended/UnattendServiceControlInstaller.cs b/src/ServiceControlInstaller.Engine/Unattended/UnattendServiceControlInstaller.cs index ff2fc16964..565e9cf783 100644 --- a/src/ServiceControlInstaller.Engine/Unattended/UnattendServiceControlInstaller.cs +++ b/src/ServiceControlInstaller.Engine/Unattended/UnattendServiceControlInstaller.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.ServiceProcess; + using System.Threading; using System.Threading.Tasks; using Configuration.ServiceControl; using FileSystem; @@ -20,7 +21,7 @@ public UnattendServiceControlInstaller(ILogging loggingInstance) public PlatformZipInfo ZipInfo { get; } - public async Task Add(ServiceControlNewInstance details, Func> promptToProceed) + public async Task Add(ServiceControlNewInstance details, Func> promptToProceed, CancellationToken cancellationToken = default) { ZipInfo.ValidateZip(); @@ -32,7 +33,7 @@ public async Task Add(ServiceControlNewInstance details, Func Update(ServiceControlInstance instance, bool startService) + internal async Task Update(ServiceControlInstance instance, bool startService, CancellationToken cancellationToken = default) { instance.ReportCard = new ReportCard(); - await instance.ValidateChanges().ConfigureAwait(false); + await instance.ValidateChanges(cancellationToken).ConfigureAwait(false); if (instance.ReportCard.HasErrors) { return false; diff --git a/src/ServiceControlInstaller.Engine/Validation/AbstractCommandChecks.cs b/src/ServiceControlInstaller.Engine/Validation/AbstractCommandChecks.cs index 9f4e2771b4..4e9363e14a 100644 --- a/src/ServiceControlInstaller.Engine/Validation/AbstractCommandChecks.cs +++ b/src/ServiceControlInstaller.Engine/Validation/AbstractCommandChecks.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.ServiceProcess; + using System.Threading; using System.Threading.Tasks; using NuGet.Versioning; using ServiceControl.LicenseManagement; @@ -14,24 +15,24 @@ public abstract class AbstractCommandChecks { protected const string UpgradeGuide4to5Url = "https://docs.particular.net/servicecontrol/upgrades/4to5/"; - protected abstract Task PromptForRabbitMqCheck(bool isUpgrade); - protected abstract Task PromptToStopRunningInstance(BaseService instance); - protected abstract Task PromptToContinueWithForcedUpgrade(); - protected abstract Task NotifyForDeprecatedMessageTransport(TransportInfo transport); - protected abstract Task NotifyForMissingSystemPrerequisites(string missingPrereqsMessage); - protected abstract Task NotifyForIncompatibleStorageEngine(IServiceControlBaseInstance baseInstance); - protected abstract Task NotifyForIncompatibleUpgradeVersion(UpgradeInfo upgradeInfo); - protected abstract Task NotifyError(string title, string message); + protected abstract Task PromptForRabbitMqCheck(bool isUpgrade, CancellationToken cancellationToken = default); + protected abstract Task PromptToStopRunningInstance(BaseService instance, CancellationToken cancellationToken = default); + protected abstract Task PromptToContinueWithForcedUpgrade(CancellationToken cancellationToken = default); + protected abstract Task NotifyForDeprecatedMessageTransport(TransportInfo transport, CancellationToken cancellationToken = default); + protected abstract Task NotifyForMissingSystemPrerequisites(string missingPrereqsMessage, CancellationToken cancellationToken = default); + protected abstract Task NotifyForIncompatibleStorageEngine(IServiceControlBaseInstance baseInstance, CancellationToken cancellationToken = default); + protected abstract Task NotifyForIncompatibleUpgradeVersion(UpgradeInfo upgradeInfo, CancellationToken cancellationToken = default); + protected abstract Task NotifyError(string title, string message, CancellationToken cancellationToken = default); - public async Task CanAddInstance(bool allowExpiredLicense = false) + public async Task CanAddInstance(bool allowExpiredLicense = false, CancellationToken cancellationToken = default) { // Check for license - if (!allowExpiredLicense && !await IsLicenseOk().ConfigureAwait(false)) + if (!allowExpiredLicense && !await IsLicenseOk(cancellationToken).ConfigureAwait(false)) { return false; } - if (await OldVersionOfServiceControlInstalled().ConfigureAwait(false)) + if (await OldVersionOfServiceControlInstalled(cancellationToken).ConfigureAwait(false)) { return false; } @@ -39,7 +40,7 @@ public async Task CanAddInstance(bool allowExpiredLicense = false) return true; } - public async Task ValidateNewInstance(params IServiceInstance[] instances) + public async Task ValidateNewInstance(IServiceInstance[] instances, CancellationToken cancellationToken = default) { var transport = instances .OfType() @@ -47,16 +48,16 @@ public async Task ValidateNewInstance(params IServiceInstance[] instances) .Select(i => i.TransportPackage) .First(t => t is not null); - var continueInstall = await RabbitMqCheckIsOK(transport, Constants.CurrentVersion, false).ConfigureAwait(false); + var continueInstall = await RabbitMqCheckIsOK(transport, Constants.CurrentVersion, false, cancellationToken).ConfigureAwait(false); return continueInstall; } - public Task CanEditInstance(BaseService instance) => CanEditOrDelete(instance, isDelete: false); + public Task CanEditInstance(BaseService instance, CancellationToken cancellationToken = default) => CanEditOrDelete(instance, isDelete: false, cancellationToken); - public Task CanDeleteInstance(BaseService instance) => CanEditOrDelete(instance, isDelete: true); + public Task CanDeleteInstance(BaseService instance, CancellationToken cancellationToken = default) => CanEditOrDelete(instance, isDelete: true, cancellationToken); - async Task CanEditOrDelete(BaseService instance, bool isDelete) + async Task CanEditOrDelete(BaseService instance, bool isDelete, CancellationToken cancellationToken) { var instanceVersion = instance.Version; var instanceIsNewer = instanceVersion > Constants.CurrentVersion; @@ -68,21 +69,21 @@ async Task CanEditOrDelete(BaseService instance, bool isDelete) { var verb = isDelete ? "remove" : "edit"; var message = $"This instance version {instanceVersion} is newer than the installer version {Constants.CurrentVersion}. This installer can only {verb} instances with versions between {Constants.CurrentVersion.Major}.0.0 and {Constants.CurrentVersion}."; - await NotifyError(title, message).ConfigureAwait(false); + await NotifyError(title, message, cancellationToken).ConfigureAwait(false); return false; } if (installerOfDifferentMajor && !isDelete) { var message = $"This installer cannot edit instances created by a different major version. A {instanceVersion.Major}.* installer version greater or equal to {instanceVersion.Major}.{instanceVersion.Minor}.{instanceVersion.Patch} must be used instead."; - await NotifyError(title, message).ConfigureAwait(false); + await NotifyError(title, message, cancellationToken).ConfigureAwait(false); return false; } return true; } - async Task RabbitMqCheckIsOK(TransportInfo transport, SemanticVersion instanceVersion, bool isUpgrade) + async Task RabbitMqCheckIsOK(TransportInfo transport, SemanticVersion instanceVersion, bool isUpgrade, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(transport); @@ -99,18 +100,18 @@ async Task RabbitMqCheckIsOK(TransportInfo transport, SemanticVersion inst return true; } - return await PromptForRabbitMqCheck(isUpgrade).ConfigureAwait(false); + return await PromptForRabbitMqCheck(isUpgrade, cancellationToken).ConfigureAwait(false); } - public async Task CanUpgradeInstance(BaseService instance, bool forceUpgradeDb = false) + public async Task CanUpgradeInstance(BaseService instance, bool forceUpgradeDb = false, CancellationToken cancellationToken = default) { // Check for license - if (!await IsLicenseOk().ConfigureAwait(false)) + if (!await IsLicenseOk(cancellationToken).ConfigureAwait(false)) { return false; } - if (await OldVersionOfServiceControlInstalled().ConfigureAwait(false)) + if (await OldVersionOfServiceControlInstalled(cancellationToken).ConfigureAwait(false)) { return false; } @@ -119,7 +120,7 @@ public async Task CanUpgradeInstance(BaseService instance, bool forceUpgra var cantUpdateTransport = instance.TransportPackage.Removed && instance.TransportPackage.AutoMigrateTo is null; if (cantUpdateTransport) { - await NotifyForDeprecatedMessageTransport(instance.TransportPackage).ConfigureAwait(false); + await NotifyForDeprecatedMessageTransport(instance.TransportPackage, cancellationToken).ConfigureAwait(false); return false; } @@ -134,11 +135,11 @@ public async Task CanUpgradeInstance(BaseService instance, bool forceUpgra if (!forceUpgradeAllowed) { - await NotifyError("Cannot run the command", "Only ServiceControl 4.x primary instances that use RavenDB 3.5 persistence can be force-upgraded.").ConfigureAwait(false); + await NotifyError("Cannot run the command", "Only ServiceControl 4.x primary instances that use RavenDB 3.5 persistence can be force-upgraded.", cancellationToken).ConfigureAwait(false); return false; } - if (!await PromptToContinueWithForcedUpgrade().ConfigureAwait(false)) + if (!await PromptToContinueWithForcedUpgrade(cancellationToken).ConfigureAwait(false)) { return false; } @@ -149,7 +150,7 @@ public async Task CanUpgradeInstance(BaseService instance, bool forceUpgra if (!compatibleStorageEngine) { - await NotifyForIncompatibleStorageEngine(baseInstance).ConfigureAwait(false); + await NotifyForIncompatibleStorageEngine(baseInstance, cancellationToken).ConfigureAwait(false); return false; } } @@ -159,12 +160,12 @@ public async Task CanUpgradeInstance(BaseService instance, bool forceUpgra var upgradeInfo = UpgradeInfo.GetUpgradePathFor(instance.Version); if (upgradeInfo.HasIncompatibleVersion) { - await NotifyForIncompatibleUpgradeVersion(upgradeInfo).ConfigureAwait(false); + await NotifyForIncompatibleUpgradeVersion(upgradeInfo, cancellationToken).ConfigureAwait(false); return false; } } - if (!await RabbitMqCheckIsOK(instance.TransportPackage, instance.Version, isUpgrade: true).ConfigureAwait(false)) + if (!await RabbitMqCheckIsOK(instance.TransportPackage, instance.Version, isUpgrade: true, cancellationToken).ConfigureAwait(false)) { return false; } @@ -172,38 +173,38 @@ public async Task CanUpgradeInstance(BaseService instance, bool forceUpgra return true; } - async Task OldVersionOfServiceControlInstalled() + async Task OldVersionOfServiceControlInstalled(CancellationToken cancellationToken) { if (OldScmuCheck.OldVersionOfServiceControlInstalled(out var installedVersion)) { var message = $"An old version {installedVersion} of ServiceControl Management is installed, which will not work after installing new instances. Before installing ServiceControl 5 instances, you must either uninstall the {installedVersion} instance or update it to a 4.x version at least {OldScmuCheck.MinimumScmuVersion}."; - await NotifyError("Outdated Version Installed", message).ConfigureAwait(false); + await NotifyError("Outdated Version Installed", message, cancellationToken).ConfigureAwait(false); return true; } return false; } - async Task IsLicenseOk() + async Task IsLicenseOk(CancellationToken cancellationToken) { var licenseCheckResult = CheckLicenseIsValid(); if (!licenseCheckResult.Valid) { - await NotifyError("License Error", $"Upgrade could not continue due to an issue with the current license. {licenseCheckResult.Message}. Contact contact@particular.net").ConfigureAwait(false); + await NotifyError("License Error", $"Upgrade could not continue due to an issue with the current license. {licenseCheckResult.Message}. Contact contact@particular.net", cancellationToken).ConfigureAwait(false); return false; } return true; } - public async Task StopBecauseInstanceIsRunning(BaseService instance) + public async Task StopBecauseInstanceIsRunning(BaseService instance, CancellationToken cancellationToken = default) { if (instance.Service.Status == ServiceControllerStatus.Stopped) { return false; } - var proceed = await PromptToStopRunningInstance(instance).ConfigureAwait(false); + var proceed = await PromptToStopRunningInstance(instance, cancellationToken).ConfigureAwait(false); return !proceed; } diff --git a/src/ServiceControlInstaller.Engine/Validation/PathsValidator.cs b/src/ServiceControlInstaller.Engine/Validation/PathsValidator.cs index b24eec3886..edfa7a5c5f 100644 --- a/src/ServiceControlInstaller.Engine/Validation/PathsValidator.cs +++ b/src/ServiceControlInstaller.Engine/Validation/PathsValidator.cs @@ -4,6 +4,7 @@ namespace ServiceControlInstaller.Engine.Validation using System.Collections.Generic; using System.IO; using System.Linq; + using System.Threading; using System.Threading.Tasks; class PathsValidator @@ -52,12 +53,12 @@ public PathsValidator(IServiceControlPaths instance) paths = pathList.Where(p => !string.IsNullOrWhiteSpace(p.Path)).ToList(); } - public Task RunValidation(bool includeNewInstanceChecks) + public Task RunValidation(bool includeNewInstanceChecks, CancellationToken cancellationToken = default) { - return RunValidation(includeNewInstanceChecks, info => Task.FromResult(false)); + return RunValidation(includeNewInstanceChecks, (info, token) => Task.FromResult(false), cancellationToken); } - public async Task RunValidation(bool includeNewInstanceChecks, Func> promptToProceed) + public async Task RunValidation(bool includeNewInstanceChecks, Func> promptToProceed, CancellationToken cancellationToken = default) { try { @@ -69,7 +70,7 @@ public async Task RunValidation(bool includeNewInstanceChecks, Func RunValidation(bool includeNewInstanceChecks, Func CheckPathsAreEmpty(Func> promptToProceed) + async Task CheckPathsAreEmpty(Func> promptToProceed, CancellationToken cancellationToken) { foreach (var pathInfo in paths) { @@ -104,7 +109,7 @@ async Task CheckPathsAreEmpty(Func> promptToProceed) if (directory.EnumerateFileSystemInfos().Any()) { - var shouldProceed = await promptToProceed(pathInfo).ConfigureAwait(false); + var shouldProceed = await promptToProceed(pathInfo, cancellationToken).ConfigureAwait(false); if (!shouldProceed) { return true;