From d83e05d34e591e6d95e3268518d4bd6594fbf228 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 18 Feb 2025 12:33:24 +0100 Subject: [PATCH 01/33] Introduce a setting to control the primary instance shutdown timeout --- src/ServiceControl/HostApplicationBuilderExtensions.cs | 2 +- src/ServiceControl/Infrastructure/Settings/Settings.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl/HostApplicationBuilderExtensions.cs b/src/ServiceControl/HostApplicationBuilderExtensions.cs index 0c365fecbc..a1774d4b30 100644 --- a/src/ServiceControl/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl/HostApplicationBuilderExtensions.cs @@ -51,7 +51,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S var transportCustomization = TransportFactory.Create(transportSettings); transportCustomization.AddTransportForPrimary(services, transportSettings); - services.Configure(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30)); + services.Configure(options => options.ShutdownTimeout = settings.ShutdownTimeout); services.AddSingleton(); services.AddSingleton(); diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 04b7a232bb..5ae680324b 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -65,6 +65,7 @@ public Settings( TimeToRestartErrorIngestionAfterFailure = GetTimeToRestartErrorIngestionAfterFailure(); DisableExternalIntegrationsPublishing = SettingsReader.Read(SettingsRootNamespace, "DisableExternalIntegrationsPublishing", false); TrackInstancesInitialValue = SettingsReader.Read(SettingsRootNamespace, "TrackInstancesInitialValue", true); + ShutdownTimeout = SettingsReader.Read(SettingsRootNamespace, "ShutdownTimeout", ShutdownTimeout); AssemblyLoadContextResolver = static assemblyPath => new PluginAssemblyLoadContext(assemblyPath); } @@ -180,6 +181,11 @@ public TimeSpan HeartbeatGracePeriod public bool DisableHealthChecks { get; set; } + // Windows services allow a maximum of 125 seconds when stopping a service. + // When shutting down or restarting the OS we have no control over the + // shutdown timeout + public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromMinutes(2); + public string GetConnectionString() { var settingsValue = SettingsReader.Read(SettingsRootNamespace, "ConnectionString"); From 0f21c3d26a333c43fb64fda9aa14586290b1332c Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 18 Feb 2025 12:33:34 +0100 Subject: [PATCH 02/33] Introduce a setting to control the audit instance shutdown timeout --- .../HostApplicationBuilderExtensions.cs | 2 +- .../Infrastructure/Settings/Settings.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs index 13d630456e..f4ed005435 100644 --- a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs @@ -45,7 +45,7 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder, var transportCustomization = TransportFactory.Create(transportSettings); transportCustomization.AddTransportForAudit(services, transportSettings); - services.Configure(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30)); + services.Configure(options => options.ShutdownTimeout = settings.ShutdownTimeout); services.AddSingleton(settings); services.AddSingleton(); diff --git a/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs b/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs index c0b1dfb892..7e89ace1f1 100644 --- a/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs @@ -49,6 +49,7 @@ public Settings(string transportType = null, string persisterType = null, Loggin ServiceControlQueueAddress = SettingsReader.Read(SettingsRootNamespace, "ServiceControlQueueAddress"); TimeToRestartAuditIngestionAfterFailure = GetTimeToRestartAuditIngestionAfterFailure(); EnableFullTextSearchOnBodies = SettingsReader.Read(SettingsRootNamespace, "EnableFullTextSearchOnBodies", true); + ShutdownTimeout = SettingsReader.Read(SettingsRootNamespace, "ShutdownTimeout", ShutdownTimeout); AssemblyLoadContextResolver = static assemblyPath => new PluginAssemblyLoadContext(assemblyPath); } @@ -152,6 +153,11 @@ public int MaxBodySizeToStore public bool EnableFullTextSearchOnBodies { get; set; } + // Windows services allow a maximum of 125 seconds when stopping a service. + // When shutting down or restarting the OS we have no control over the + // shutdown timeout + public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromMinutes(2); + public TransportSettings ToTransportSettings() { var transportSettings = new TransportSettings From 34cfc44dace0c92fe1b0f67c0ab563d2afb35e06 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 18 Feb 2025 12:54:57 +0100 Subject: [PATCH 03/33] Approved API --- .../APIApprovals.PlatformSampleSettings.approved.txt | 3 ++- .../APIApprovals.PlatformSampleSettings.approved.txt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index 77b2ef7666..97fee174ed 100644 --- a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -28,5 +28,6 @@ "MaximumConcurrencyLevel": null, "ServiceControlQueueAddress": "Particular.ServiceControl", "TimeToRestartAuditIngestionAfterFailure": "00:01:00", - "EnableFullTextSearchOnBodies": true + "EnableFullTextSearchOnBodies": true, + "ShutdownTimeout": "00:02:00" } \ No newline at end of file diff --git a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index 98ed016ffb..c3502fee11 100644 --- a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -42,5 +42,6 @@ "MaximumConcurrencyLevel": null, "RetryHistoryDepth": 10, "RemoteInstances": [], - "DisableHealthChecks": false + "DisableHealthChecks": false, + "ShutdownTimeout": "00:02:00" } \ No newline at end of file From 63b33ac99cbc2c04ef47cba609502eb0efab0ad8 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 18 Feb 2025 17:12:58 +0100 Subject: [PATCH 04/33] Set the default shutdown timeout to the max allowed by the most restrictive hosting platform --- src/ServiceControl/Infrastructure/Settings/Settings.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 5ae680324b..492c3d0917 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -181,10 +181,9 @@ public TimeSpan HeartbeatGracePeriod public bool DisableHealthChecks { get; set; } - // Windows services allow a maximum of 125 seconds when stopping a service. - // When shutting down or restarting the OS we have no control over the - // shutdown timeout - public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromMinutes(2); + // The default value is set to the maximum allowed time by the most restrictive + // hosting platform, which Docker Linux containers. + public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(10); public string GetConnectionString() { From 1305f0fdda60cc629e6aa6d8d873fb7778388665 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 18 Feb 2025 17:13:52 +0100 Subject: [PATCH 05/33] When running the installer engine (via SCMU or PowerShell) we can assume it is Windows, and thus we can set the shutdown timeout to 2 minutes --- .../Configuration/ServiceControl/ServiceControlAppConfig.cs | 6 ++++++ .../Configuration/ServiceControl/SettingsList.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs index 950058cfa7..c94ca3f13e 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs @@ -32,6 +32,12 @@ protected override void UpdateSettings() settings.Set(ServiceControlSettings.EnableFullTextSearchOnBodies, details.EnableFullTextSearchOnBodies.ToString(), version); settings.Set(ServiceControlSettings.RemoteInstances, RemoteInstanceConverter.ToJson(details.RemoteInstances), version); + // Windows services allow a maximum of 125 seconds when stopping a service. + // When shutting down or restarting the OS we have no control over the + // shutdown timeout. This is by the installer engine that is run _only_ on + // Windows via SCMU or PowerShell + settings.Set(ServiceControlSettings.ShutdownTimeout, "00:02:00"); + // Retired settings settings.RemoveIfRetired(ServiceControlSettings.AuditQueue, version); settings.RemoveIfRetired(ServiceControlSettings.AuditLogQueue, version); diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs index bd0b5e4c87..5f324db1f8 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs @@ -89,5 +89,11 @@ public static class ServiceControlSettings Name = "ServiceControl/EnableFullTextSearchOnBodies", SupportedFrom = new SemanticVersion(4, 17, 0) }; + + public static SettingInfo ShutdownTimeout = new() + { + Name = "ServiceControl/ShutdownTimeout", + SupportedFrom = new SemanticVersion(6, 4, 0) + }; } } \ No newline at end of file From 0a93c9e9f13ac3e7a8c5d561ea76bf8fa4a45461 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 11:24:07 +0100 Subject: [PATCH 06/33] Ensure the ShutdownTimeout is set to 2 minutes when installing/updating audit instances --- .../ServiceControl/ServiceControlAuditAppConfig.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs index bcbae4a3e1..f675650088 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs @@ -30,6 +30,12 @@ protected override void UpdateSettings() settings.Set(AuditInstanceSettingsList.ServiceControlQueueAddress, instance.ServiceControlQueueAddress); settings.Set(AuditInstanceSettingsList.EnableFullTextSearchOnBodies, instance.EnableFullTextSearchOnBodies.ToString().ToLowerInvariant(), version); + // Windows services allow a maximum of 125 seconds when stopping a service. + // When shutting down or restarting the OS we have no control over the + // shutdown timeout. This is by the installer engine that is run _only_ on + // Windows via SCMU or PowerShell + settings.Set(ServiceControlSettings.ShutdownTimeout, "00:02:00"); + foreach (var manifestSetting in instance.PersistenceManifest.Settings) { if (!settings.AllKeys.Contains(manifestSetting.Name)) From 6bbca4a6c436c14901e9628e4ac305da39c0a9e7 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 11:34:28 +0100 Subject: [PATCH 07/33] Update src/ServiceControl/Infrastructure/Settings/Settings.cs Co-authored-by: Ramon Smits --- src/ServiceControl/Infrastructure/Settings/Settings.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 492c3d0917..375adb6866 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -182,8 +182,9 @@ public TimeSpan HeartbeatGracePeriod public bool DisableHealthChecks { get; set; } // The default value is set to the maximum allowed time by the most restrictive - // hosting platform, which Docker Linux containers. - public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(10); + // hosting platform, which Docker Linux containers minus a few seconds + // to actually allow for cancellation and logging to take place + public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); public string GetConnectionString() { From c401f3855f9cca206963c3f811b327c1b8ec46b9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 11:40:44 +0100 Subject: [PATCH 08/33] Set the default shutdown timeout to 5 seconds --- .../APIApprovals.PlatformSampleSettings.approved.txt | 2 +- .../Infrastructure/Settings/Settings.cs | 9 +++++---- .../APIApprovals.PlatformSampleSettings.approved.txt | 2 +- src/ServiceControl/Infrastructure/Settings/Settings.cs | 7 ++++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index 97fee174ed..f718f461aa 100644 --- a/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.Audit.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -29,5 +29,5 @@ "ServiceControlQueueAddress": "Particular.ServiceControl", "TimeToRestartAuditIngestionAfterFailure": "00:01:00", "EnableFullTextSearchOnBodies": true, - "ShutdownTimeout": "00:02:00" + "ShutdownTimeout": "00:00:05" } \ No newline at end of file diff --git a/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs b/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs index 7e89ace1f1..9108c7d87f 100644 --- a/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs @@ -153,10 +153,11 @@ public int MaxBodySizeToStore public bool EnableFullTextSearchOnBodies { get; set; } - // Windows services allow a maximum of 125 seconds when stopping a service. - // When shutting down or restarting the OS we have no control over the - // shutdown timeout - public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromMinutes(2); + // The default value is set to the maximum allowed time by the most + // restrictive hosting platform, which is Linux containers. Linux + // containers allow for a maximum of 10 seconds. We set it to 5 to + // allow for cancellation and logging to take place + public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); public TransportSettings ToTransportSettings() { diff --git a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt index c3502fee11..a23480af26 100644 --- a/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.UnitTests/ApprovalFiles/APIApprovals.PlatformSampleSettings.approved.txt @@ -43,5 +43,5 @@ "RetryHistoryDepth": 10, "RemoteInstances": [], "DisableHealthChecks": false, - "ShutdownTimeout": "00:02:00" + "ShutdownTimeout": "00:00:05" } \ No newline at end of file diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 375adb6866..cf6dbeffe7 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -181,9 +181,10 @@ public TimeSpan HeartbeatGracePeriod public bool DisableHealthChecks { get; set; } - // The default value is set to the maximum allowed time by the most restrictive - // hosting platform, which Docker Linux containers minus a few seconds - // to actually allow for cancellation and logging to take place + // The default value is set to the maximum allowed time by the most + // restrictive hosting platform, which is Linux containers. Linux + // containers allow for a maximum of 10 seconds. We set it to 5 to + // allow for cancellation and logging to take place public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); public string GetConnectionString() From 7e58847053111bf576e50d592723bd56475a7564 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 11:44:22 +0000 Subject: [PATCH 09/33] Add the required version --- .../Configuration/ServiceControl/ServiceControlAppConfig.cs | 2 +- .../ServiceControl/ServiceControlAuditAppConfig.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs index c94ca3f13e..c00b1d8ee5 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs @@ -36,7 +36,7 @@ protected override void UpdateSettings() // When shutting down or restarting the OS we have no control over the // shutdown timeout. This is by the installer engine that is run _only_ on // Windows via SCMU or PowerShell - settings.Set(ServiceControlSettings.ShutdownTimeout, "00:02:00"); + settings.Set(ServiceControlSettings.ShutdownTimeout, "00:02:00", version); // Retired settings settings.RemoveIfRetired(ServiceControlSettings.AuditQueue, version); diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs index f675650088..ba8e4c38b5 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs @@ -34,7 +34,7 @@ protected override void UpdateSettings() // When shutting down or restarting the OS we have no control over the // shutdown timeout. This is by the installer engine that is run _only_ on // Windows via SCMU or PowerShell - settings.Set(ServiceControlSettings.ShutdownTimeout, "00:02:00"); + settings.Set(ServiceControlSettings.ShutdownTimeout, "00:02:00", version); foreach (var manifestSetting in instance.PersistenceManifest.Settings) { From 135e9efaa006c3e4c1e5f637c7be72bb2fc387aa Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 11:50:23 +0000 Subject: [PATCH 10/33] Fix approved files adding ShutdownTimeout --- ...nstall_should_write_expected_config_file.RavenDB.approved.txt | 1 + ...ts.Should_install_modern_raven_for_new_instances.approved.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt index 16ddbafe24..26a070829f 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt +++ b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt @@ -10,6 +10,7 @@ + diff --git a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt index 16ddbafe24..26a070829f 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt +++ b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt @@ -10,6 +10,7 @@ + From eb438d85a38ca3ec2ad448238cc0c465dede42bc Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 13:56:03 +0100 Subject: [PATCH 11/33] Add the ShutdownTimeout to the audit instance settings --- .../ServiceControl/AuditInstanceSettingsList.cs | 6 ++++++ .../ServiceControl/ServiceControlAuditAppConfig.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs index a2b0800cb7..71828ab314 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs @@ -35,5 +35,11 @@ public static class AuditInstanceSettingsList Name = "ServiceControl.Audit/EnableFullTextSearchOnBodies", SupportedFrom = new SemanticVersion(4, 17, 0) }; + + public static readonly SettingInfo ShutdownTimeout = new() + { + Name = "ServiceControl/ShutdownTimeout", + SupportedFrom = new SemanticVersion(6, 4, 1) + }; } } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs index ba8e4c38b5..81386283eb 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAuditAppConfig.cs @@ -34,7 +34,7 @@ protected override void UpdateSettings() // When shutting down or restarting the OS we have no control over the // shutdown timeout. This is by the installer engine that is run _only_ on // Windows via SCMU or PowerShell - settings.Set(ServiceControlSettings.ShutdownTimeout, "00:02:00", version); + settings.Set(AuditInstanceSettingsList.ShutdownTimeout, "00:02:00", version); foreach (var manifestSetting in instance.PersistenceManifest.Settings) { From 8eb5b48bd4d3cfb57ba90acb2e0eddf713131297 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 13:57:20 +0100 Subject: [PATCH 12/33] Add the ShutdownTimeout to the Monitoring instance --- .../HostApplicationBuilderExtensions.cs | 2 ++ src/ServiceControl.Monitoring/Settings.cs | 7 +++++++ .../Configuration/Monitoring/AppConfig.cs | 6 ++++++ .../Configuration/Monitoring/SettingsList.cs | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index 06e6e2b386..b88f1340c3 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -39,6 +39,8 @@ public static void AddServiceControlMonitoring(this IHostApplicationBuilder host var transportCustomization = TransportFactory.Create(transportSettings); transportCustomization.AddTransportForMonitoring(services, transportSettings); + services.Configure(options => options.ShutdownTimeout = settings.ShutdownTimeout); + services.AddWindowsService(); services.AddSingleton(settings); diff --git a/src/ServiceControl.Monitoring/Settings.cs b/src/ServiceControl.Monitoring/Settings.cs index 6900919e78..3412307042 100644 --- a/src/ServiceControl.Monitoring/Settings.cs +++ b/src/ServiceControl.Monitoring/Settings.cs @@ -39,6 +39,7 @@ public Settings(LoggingSettings loggingSettings = null, string transportType = n EndpointUptimeGracePeriod = TimeSpan.Parse(SettingsReader.Read(SettingsRootNamespace, "EndpointUptimeGracePeriod", "00:00:40")); MaximumConcurrencyLevel = SettingsReader.Read(SettingsRootNamespace, "MaximumConcurrencyLevel"); ServiceControlThroughputDataQueue = SettingsReader.Read(SettingsRootNamespace, "ServiceControlThroughputDataQueue", "ServiceControl.ThroughputData"); + ShutdownTimeout = SettingsReader.Read(SettingsRootNamespace, "ShutdownTimeout", ShutdownTimeout); AssemblyLoadContextResolver = static assemblyPath => new PluginAssemblyLoadContext(assemblyPath); } @@ -68,6 +69,12 @@ public Settings(LoggingSettings loggingSettings = null, string transportType = n public string ServiceControlThroughputDataQueue { get; set; } + // The default value is set to the maximum allowed time by the most + // restrictive hosting platform, which is Linux containers. Linux + // containers allow for a maximum of 10 seconds. We set it to 5 to + // allow for cancellation and logging to take place + public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); + public TransportSettings ToTransportSettings() { var transportSettings = new TransportSettings diff --git a/src/ServiceControlInstaller.Engine/Configuration/Monitoring/AppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/Monitoring/AppConfig.cs index 3a0c42d636..ce845f1ca2 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/Monitoring/AppConfig.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/Monitoring/AppConfig.cs @@ -22,6 +22,12 @@ public void Save() settings.Set(SettingsList.TransportType, details.TransportPackage.Name, version); settings.Set(SettingsList.ErrorQueue, details.ErrorQueue); + // Windows services allow a maximum of 125 seconds when stopping a service. + // When shutting down or restarting the OS we have no control over the + // shutdown timeout. This is by the installer engine that is run _only_ on + // Windows via SCMU or PowerShell + settings.Set(SettingsList.ShutdownTimeout, "00:02:00", version); + // Retired settings settings.RemoveIfRetired(SettingsList.EndpointName, version); diff --git a/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs index c8aed89e42..e7651cd20e 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/Monitoring/SettingsList.cs @@ -21,5 +21,10 @@ public static class SettingsList public static SettingInfo LogPath = new() { Name = "Monitoring/LogPath" }; public static SettingInfo TransportType = new() { Name = "Monitoring/TransportType" }; public static SettingInfo ErrorQueue = new() { Name = "Monitoring/ErrorQueue" }; + public static SettingInfo ShutdownTimeout = new() + { + Name = "Monitoring/ShutdownTimeout", + SupportedFrom = new SemanticVersion(6, 4, 1) + }; } } \ No newline at end of file From 7c0a3e05ee4680d7b36eb472d6ad3067d86de127 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 13:58:02 +0100 Subject: [PATCH 13/33] Fix the settings namespace --- .../Configuration/ServiceControl/AuditInstanceSettingsList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs index 71828ab314..d557bf4bcf 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/AuditInstanceSettingsList.cs @@ -38,7 +38,7 @@ public static class AuditInstanceSettingsList public static readonly SettingInfo ShutdownTimeout = new() { - Name = "ServiceControl/ShutdownTimeout", + Name = "ServiceControl.Audit/ShutdownTimeout", SupportedFrom = new SemanticVersion(6, 4, 1) }; } From 93de0acba3c6f99a7f11c43b4a5a6a2c615fdfda Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 15:01:24 +0100 Subject: [PATCH 14/33] Approval files --- .../SettingsTests.PlatformSampleSettings.approved.txt | 3 ++- ...tall_should_write_expected_config_file.RavenDB.approved.txt | 2 +- ....Should_install_modern_raven_for_new_instances.approved.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt b/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt index b855b960fc..5d32f42fbb 100644 --- a/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt +++ b/src/ServiceControl.Monitoring.UnitTests/ApprovalFiles/SettingsTests.PlatformSampleSettings.approved.txt @@ -15,5 +15,6 @@ "EndpointUptimeGracePeriod": "00:00:40", "RootUrl": "http://localhost:9999/", "MaximumConcurrencyLevel": null, - "ServiceControlThroughputDataQueue": "ServiceControl.ThroughputData" + "ServiceControlThroughputDataQueue": "ServiceControl.ThroughputData", + "ShutdownTimeout": "00:00:05" } \ No newline at end of file diff --git a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt index 26a070829f..5050490958 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt +++ b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/InstallationTests.Audit_install_should_write_expected_config_file.RavenDB.approved.txt @@ -10,7 +10,7 @@ - + diff --git a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt index 26a070829f..5050490958 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt +++ b/src/ServiceControlInstaller.Engine.UnitTests/ApprovalFiles/NewAuditInstanceTests.Should_install_modern_raven_for_new_instances.approved.txt @@ -10,7 +10,7 @@ - + From 780acffdb9e61e52ae3b40241e50956a5877e5a5 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 15:02:28 +0100 Subject: [PATCH 15/33] Set the SemanticVersion for the ShutdownTimeout to 6.4.1 --- .../Configuration/ServiceControl/SettingsList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs index 5f324db1f8..f982b8bc34 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs @@ -93,7 +93,7 @@ public static class ServiceControlSettings public static SettingInfo ShutdownTimeout = new() { Name = "ServiceControl/ShutdownTimeout", - SupportedFrom = new SemanticVersion(6, 4, 0) + SupportedFrom = new SemanticVersion(6, 4, 1) }; } } \ No newline at end of file From a8292eb526b3e26809b6a4c44deee9145b510af9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 15:14:30 +0100 Subject: [PATCH 16/33] Surround AddWindowsService() with a check to validate it's running as a service --- .../HostApplicationBuilderExtensions.cs | 8 +++++++- .../Hosting/Commands/MaintenanceModeCommand.cs | 8 +++++++- .../HostApplicationBuilderExtensions.cs | 8 +++++++- src/ServiceControl/HostApplicationBuilderExtensions.cs | 8 +++++++- .../Hosting/Commands/MaintenanceModeCommand.cs | 8 +++++++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs index f4ed005435..87f1dc945d 100644 --- a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs @@ -10,6 +10,7 @@ namespace ServiceControl.Audit; using Microsoft.AspNetCore.HttpLogging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; using Microsoft.Extensions.Logging; using Monitoring; using NLog.Extensions.Logging; @@ -100,7 +101,12 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder, services.AddHostedService(); } - builder.Services.AddWindowsService(); + if (WindowsServiceHelpers.IsWindowsService()) + { + // The if is added for clarity, internally AddWindowsService has a similar logic + builder.Services.AddWindowsService(); + //TODO register our own lifecycle that replaces the WindowsService default one + } } public static void AddServiceControlAuditInstallers(this IHostApplicationBuilder builder, Settings settings) diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs index 0f23b2e771..1ea88bf551 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs @@ -2,6 +2,7 @@ { using System.Threading.Tasks; using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Hosting.WindowsServices; using Persistence; using Settings; @@ -17,7 +18,12 @@ public override async Task Execute(HostArguments args, Settings settings) var hostBuilder = Host.CreateApplicationBuilder(); hostBuilder.Services.AddPersistence(persistenceSettings, persistenceConfiguration); - hostBuilder.Services.AddWindowsService(); + if (WindowsServiceHelpers.IsWindowsService()) + { + // The if is added for clarity, internally AddWindowsService has a similar logic + hostBuilder.Services.AddWindowsService(); + //TODO register our own lifecycle that replaces the WindowsService default one + } var host = hostBuilder.Build(); await host.RunAsync(); diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index b88f1340c3..14ce6cbc11 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -13,6 +13,7 @@ namespace ServiceControl.Monitoring; using Microsoft.AspNetCore.HttpLogging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using NServiceBus; @@ -41,7 +42,12 @@ public static void AddServiceControlMonitoring(this IHostApplicationBuilder host services.Configure(options => options.ShutdownTimeout = settings.ShutdownTimeout); - services.AddWindowsService(); + if (WindowsServiceHelpers.IsWindowsService()) + { + // The if is added for clarity, internally AddWindowsService has a similar logic + services.AddWindowsService(); + //TODO register our own lifecycle that replaces the WindowsService default one + } services.AddSingleton(settings); services.AddSingleton(); diff --git a/src/ServiceControl/HostApplicationBuilderExtensions.cs b/src/ServiceControl/HostApplicationBuilderExtensions.cs index a1774d4b30..154342c80c 100644 --- a/src/ServiceControl/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl/HostApplicationBuilderExtensions.cs @@ -17,6 +17,7 @@ namespace Particular.ServiceControl using Microsoft.AspNetCore.HttpLogging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Hosting.WindowsServices; using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using NServiceBus; @@ -95,7 +96,12 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S hostBuilder.AddInternalCustomChecks(); } - hostBuilder.Services.AddWindowsService(); + if (WindowsServiceHelpers.IsWindowsService()) + { + // The if is added for clarity, internally AddWindowsService has a similar logic + hostBuilder.Services.AddWindowsService(); + //TODO register our own lifecycle that replaces the WindowsService default one + } hostBuilder.AddServiceControlComponents(settings, transportCustomization, ServiceControlMainInstance.Components); } diff --git a/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs b/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs index 95f1a13421..48fa5462bc 100644 --- a/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs +++ b/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs @@ -2,6 +2,7 @@ { using System.Threading.Tasks; using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Hosting.WindowsServices; using Particular.ServiceControl.Hosting; using Persistence; using ServiceBus.Management.Infrastructure.Settings; @@ -13,7 +14,12 @@ public override async Task Execute(HostArguments args, Settings settings) var hostBuilder = Host.CreateApplicationBuilder(); hostBuilder.Services.AddPersistence(settings, maintenanceMode: true); - hostBuilder.Services.AddWindowsService(); + if (WindowsServiceHelpers.IsWindowsService()) + { + // The if is added for clarity, internally AddWindowsService has a similar logic + hostBuilder.Services.AddWindowsService(); + //TODO register our own lifecycle that replaces the WindowsService default one + } var host = hostBuilder.Build(); await host.RunAsync(); From 0f4c0976899420f299edfbd74899c4156ed00474 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 15:24:15 +0100 Subject: [PATCH 17/33] Add the WindowsServiceCustomLifetime to request additional time on stop to the audit instance --- .../HostApplicationBuilderExtensions.cs | 3 +- .../Hosting/WindowsServiceCustomLifetime.cs | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs diff --git a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs index 87f1dc945d..d676494246 100644 --- a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs @@ -6,6 +6,7 @@ namespace ServiceControl.Audit; using System.Threading.Tasks; using Auditing; using Infrastructure; +using Infrastructure.Hosting; using Infrastructure.Settings; using Microsoft.AspNetCore.HttpLogging; using Microsoft.Extensions.DependencyInjection; @@ -105,7 +106,7 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder, { // The if is added for clarity, internally AddWindowsService has a similar logic builder.Services.AddWindowsService(); - //TODO register our own lifecycle that replaces the WindowsService default one + builder.Services.AddSingleton(); } } diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs new file mode 100644 index 0000000000..cef5253e7e --- /dev/null +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs @@ -0,0 +1,30 @@ +namespace ServiceControl.Audit.Infrastructure.Hosting; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Settings; + +class WindowsServiceCustomLifetime : WindowsServiceLifetime +{ + public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, Settings settings) + : base(environment, applicationLifetime, loggerFactory, optionsAccessor) + { + this.settings = settings; + } + + public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor, Settings settings) + : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) + { + this.settings = settings; + } + + protected override void OnStop() + { + RequestAdditionalTime(settings.ShutdownTimeout); + base.OnStop(); + } + + readonly Settings settings; +} \ No newline at end of file From c29fa13a62f4d8d893e6b7fa60fd15165542bc5b Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 15:45:28 +0100 Subject: [PATCH 18/33] Suppress CA1416 --- .../Infrastructure/Hosting/WindowsServiceCustomLifetime.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs index cef5253e7e..9821f469af 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs @@ -6,6 +6,7 @@ namespace ServiceControl.Audit.Infrastructure.Hosting; using Microsoft.Extensions.Options; using Settings; +#pragma warning disable CA1416 class WindowsServiceCustomLifetime : WindowsServiceLifetime { public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, Settings settings) @@ -27,4 +28,5 @@ protected override void OnStop() } readonly Settings settings; -} \ No newline at end of file +} +#pragma warning restore CA1416 \ No newline at end of file From 34d12c88a9bc29a67ff6211cbfbb35d425a4237d Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Tue, 4 Mar 2025 16:58:58 +0100 Subject: [PATCH 19/33] Extracted into separate project, support OnShutdown, using `SupportedOSPlatform` instead of suppressing CA1416 --- .../HostApplicationBuilderExtensions.cs | 5 ++- .../Hosting/WindowsServiceCustomLifetime.cs | 32 ------------------- .../ServiceControl.Audit.csproj | 1 + .../IHostApplicationBuilderExtensions.cs | 17 ++++++++++ .../ServiceControl.Hosting.csproj | 11 +++++++ .../HostApplicationBuilderExtensions.cs | 4 +-- .../ServiceControl.Monitoring.csproj | 1 + src/ServiceControl.sln | 28 ++++++++-------- .../HostApplicationBuilderExtensions.cs | 4 +-- .../Commands/MaintenanceModeCommand.cs | 3 +- src/ServiceControl/ServiceControl.csproj | 1 + 11 files changed, 52 insertions(+), 55 deletions(-) delete mode 100644 src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs create mode 100644 src/ServiceControl.Hosting/IHostApplicationBuilderExtensions.cs create mode 100644 src/ServiceControl.Hosting/ServiceControl.Hosting.csproj diff --git a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs index d676494246..c38e023869 100644 --- a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs @@ -5,8 +5,8 @@ namespace ServiceControl.Audit; using System.Threading; using System.Threading.Tasks; using Auditing; +using Hosting; using Infrastructure; -using Infrastructure.Hosting; using Infrastructure.Settings; using Microsoft.AspNetCore.HttpLogging; using Microsoft.Extensions.DependencyInjection; @@ -105,8 +105,7 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder, if (WindowsServiceHelpers.IsWindowsService()) { // The if is added for clarity, internally AddWindowsService has a similar logic - builder.Services.AddWindowsService(); - builder.Services.AddSingleton(); + builder.AddWindowsServiceWithRequestTimeout(); } } diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs deleted file mode 100644 index 9821f469af..0000000000 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/WindowsServiceCustomLifetime.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace ServiceControl.Audit.Infrastructure.Hosting; - -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Hosting.WindowsServices; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Settings; - -#pragma warning disable CA1416 -class WindowsServiceCustomLifetime : WindowsServiceLifetime -{ - public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, Settings settings) - : base(environment, applicationLifetime, loggerFactory, optionsAccessor) - { - this.settings = settings; - } - - public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor, Settings settings) - : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) - { - this.settings = settings; - } - - protected override void OnStop() - { - RequestAdditionalTime(settings.ShutdownTimeout); - base.OnStop(); - } - - readonly Settings settings; -} -#pragma warning restore CA1416 \ No newline at end of file diff --git a/src/ServiceControl.Audit/ServiceControl.Audit.csproj b/src/ServiceControl.Audit/ServiceControl.Audit.csproj index 8f41ba97b1..6e0a889e9f 100644 --- a/src/ServiceControl.Audit/ServiceControl.Audit.csproj +++ b/src/ServiceControl.Audit/ServiceControl.Audit.csproj @@ -20,6 +20,7 @@ + diff --git a/src/ServiceControl.Hosting/IHostApplicationBuilderExtensions.cs b/src/ServiceControl.Hosting/IHostApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..303595b337 --- /dev/null +++ b/src/ServiceControl.Hosting/IHostApplicationBuilderExtensions.cs @@ -0,0 +1,17 @@ +namespace ServiceControl.Hosting; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; + +public static class IHostApplicationBuilderExtensions +{ + public static void AddWindowsServiceWithRequestTimeout(this IHostApplicationBuilder builder) + { + if (WindowsServiceHelpers.IsWindowsService()) + { + builder.Services.AddWindowsService(); + builder.Services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj b/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj new file mode 100644 index 0000000000..284466ba33 --- /dev/null +++ b/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index 14ce6cbc11..90adce523d 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -5,6 +5,7 @@ namespace ServiceControl.Monitoring; using System.Threading; using System.Threading.Tasks; using Configuration; +using Hosting; using Infrastructure; using Infrastructure.BackgroundTasks; using Infrastructure.Extensions; @@ -45,8 +46,7 @@ public static void AddServiceControlMonitoring(this IHostApplicationBuilder host if (WindowsServiceHelpers.IsWindowsService()) { // The if is added for clarity, internally AddWindowsService has a similar logic - services.AddWindowsService(); - //TODO register our own lifecycle that replaces the WindowsService default one + hostBuilder.AddWindowsServiceWithRequestTimeout(); } services.AddSingleton(settings); diff --git a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj index 6314e0699b..35246ea1b8 100644 --- a/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj +++ b/src/ServiceControl.Monitoring/ServiceControl.Monitoring.csproj @@ -18,6 +18,7 @@ + diff --git a/src/ServiceControl.sln b/src/ServiceControl.sln index 7c57134866..bcd089903c 100644 --- a/src/ServiceControl.sln +++ b/src/ServiceControl.sln @@ -183,7 +183,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControl.Transports.P EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControl.Transports.PostgreSql.Tests", "ServiceControl.Transports.PostgreSql.Tests\ServiceControl.Transports.PostgreSql.Tests.csproj", "{18DBEEF5-42EE-4C1D-A05B-87B21C067D53}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetupProcessFake", "SetupProcessFake\SetupProcessFake.csproj", "{36D53BA0-C1E1-4D74-81AE-C33B40C84958}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceControl.Hosting", "ServiceControl.Hosting\ServiceControl.Hosting.csproj", "{481032A1-1106-4C6C-B75E-512F2FB08882}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -999,18 +999,18 @@ Global {18DBEEF5-42EE-4C1D-A05B-87B21C067D53}.Release|x64.Build.0 = Release|Any CPU {18DBEEF5-42EE-4C1D-A05B-87B21C067D53}.Release|x86.ActiveCfg = Release|Any CPU {18DBEEF5-42EE-4C1D-A05B-87B21C067D53}.Release|x86.Build.0 = Release|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Debug|Any CPU.Build.0 = Debug|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Debug|x64.ActiveCfg = Debug|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Debug|x64.Build.0 = Debug|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Debug|x86.ActiveCfg = Debug|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Debug|x86.Build.0 = Debug|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Release|Any CPU.ActiveCfg = Release|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Release|Any CPU.Build.0 = Release|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Release|x64.ActiveCfg = Release|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Release|x64.Build.0 = Release|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Release|x86.ActiveCfg = Release|Any CPU - {36D53BA0-C1E1-4D74-81AE-C33B40C84958}.Release|x86.Build.0 = Release|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Debug|Any CPU.Build.0 = Debug|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Debug|x64.ActiveCfg = Debug|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Debug|x64.Build.0 = Debug|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Debug|x86.ActiveCfg = Debug|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Debug|x86.Build.0 = Debug|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|Any CPU.ActiveCfg = Release|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|Any CPU.Build.0 = Release|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|x64.ActiveCfg = Release|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|x64.Build.0 = Release|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|x86.ActiveCfg = Release|Any CPU + {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1094,7 +1094,7 @@ Global {51F5504E-E915-40EC-B96E-CA700A57982C} = {80C55E70-4B7A-4EF2-BB9E-C42F8DB0495D} {448CBDCF-718D-4BC7-8F7C-099C9A362B59} = {A21A1A89-0B07-4E87-8E3C-41D9C280DCB8} {18DBEEF5-42EE-4C1D-A05B-87B21C067D53} = {E0E45F22-35E3-4AD8-B09E-EFEA5A2F18EE} - {36D53BA0-C1E1-4D74-81AE-C33B40C84958} = {927A078A-E271-4878-A153-86D71AE510E2} + {481032A1-1106-4C6C-B75E-512F2FB08882} = {9AF9D3C7-E859-451B-BA4D-B954D289213A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3B9E5B72-F580-465A-A22C-2D2148AF4EB4} diff --git a/src/ServiceControl/HostApplicationBuilderExtensions.cs b/src/ServiceControl/HostApplicationBuilderExtensions.cs index 154342c80c..50d0733fb9 100644 --- a/src/ServiceControl/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl/HostApplicationBuilderExtensions.cs @@ -5,6 +5,7 @@ namespace Particular.ServiceControl using System.Runtime.InteropServices; using global::ServiceControl.CustomChecks; using global::ServiceControl.ExternalIntegrations; + using global::ServiceControl.Hosting; using global::ServiceControl.Infrastructure.BackgroundTasks; using global::ServiceControl.Infrastructure.DomainEvents; using global::ServiceControl.Infrastructure.Metrics; @@ -99,8 +100,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S if (WindowsServiceHelpers.IsWindowsService()) { // The if is added for clarity, internally AddWindowsService has a similar logic - hostBuilder.Services.AddWindowsService(); - //TODO register our own lifecycle that replaces the WindowsService default one + hostBuilder.AddWindowsServiceWithRequestTimeout(); } hostBuilder.AddServiceControlComponents(settings, transportCustomization, ServiceControlMainInstance.Components); diff --git a/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs b/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs index 48fa5462bc..f3ba85e132 100644 --- a/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs +++ b/src/ServiceControl/Hosting/Commands/MaintenanceModeCommand.cs @@ -17,8 +17,7 @@ public override async Task Execute(HostArguments args, Settings settings) if (WindowsServiceHelpers.IsWindowsService()) { // The if is added for clarity, internally AddWindowsService has a similar logic - hostBuilder.Services.AddWindowsService(); - //TODO register our own lifecycle that replaces the WindowsService default one + hostBuilder.AddWindowsServiceWithRequestTimeout(); } var host = hostBuilder.Build(); diff --git a/src/ServiceControl/ServiceControl.csproj b/src/ServiceControl/ServiceControl.csproj index 9cd517591d..04f5956ccf 100644 --- a/src/ServiceControl/ServiceControl.csproj +++ b/src/ServiceControl/ServiceControl.csproj @@ -24,6 +24,7 @@ + From 825ead56423a9b62042cc85dd391c1a9e401d7de Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Tue, 4 Mar 2025 17:10:45 +0100 Subject: [PATCH 20/33] fixup! Extracted into separate project, support OnShutdown, using `SupportedOSPlatform` instead of suppressing CA1416 --- .../WindowsServiceWithRequestTimeout.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs diff --git a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs new file mode 100644 index 0000000000..af373056ab --- /dev/null +++ b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs @@ -0,0 +1,40 @@ +namespace ServiceControl.Hosting; + +using System; +using System.Runtime.Versioning; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +[SupportedOSPlatform("windows")] +sealed class WindowsServiceWithRequestTimeout : WindowsServiceLifetime +{ + static readonly TimeSpan CancellationDuration = TimeSpan.FromSeconds(5); + readonly HostOptions hostOptions; + + // TODO: I don't think this constructor is needed and exist for backwards compability in the runtime + + // public WindowsServiceWithRequestTimeout(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor) + // : this(environment, applicationLifetime, loggerFactory, optionsAccessor, Options.Create(new WindowsServiceLifetimeOptions())) + // { + // } + + public WindowsServiceWithRequestTimeout(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor) + : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) + { + hostOptions = optionsAccessor.Value; + } + + protected override void OnStop() + { + RequestAdditionalTime(hostOptions.ShutdownTimeout + CancellationDuration); + base.OnStop(); + } + + protected override void OnShutdown() + { + RequestAdditionalTime(hostOptions.ShutdownTimeout + CancellationDuration); + base.OnShutdown(); + } +} \ No newline at end of file From 4a9ead7d978732f1ab58e397fd7322763c9d3979 Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Tue, 4 Mar 2025 17:28:47 +0100 Subject: [PATCH 21/33] Forgot MaintenanceModeCommand --- .../Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs index 1ea88bf551..4a92ce6b41 100644 --- a/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs +++ b/src/ServiceControl.Audit/Infrastructure/Hosting/Commands/MaintenanceModeCommand.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.WindowsServices; using Persistence; + using ServiceControl.Hosting; using Settings; class MaintenanceModeCommand : AbstractCommand @@ -21,8 +22,7 @@ public override async Task Execute(HostArguments args, Settings settings) if (WindowsServiceHelpers.IsWindowsService()) { // The if is added for clarity, internally AddWindowsService has a similar logic - hostBuilder.Services.AddWindowsService(); - //TODO register our own lifecycle that replaces the WindowsService default one + hostBuilder.AddWindowsServiceWithRequestTimeout(); } var host = hostBuilder.Build(); From a662a732c949aa81c816ba0b8a13c26cae9aa0d3 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 17:29:07 +0100 Subject: [PATCH 22/33] Add the monitoring instance custom lifecycle to request additional time on stop --- .../Hosting/WindowsServiceCustomLifetime.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs diff --git a/src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs b/src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs new file mode 100644 index 0000000000..57f5954dc4 --- /dev/null +++ b/src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs @@ -0,0 +1,31 @@ +namespace ServiceControl.Monitoring.Hosting; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +#pragma warning disable CA1416 +class WindowsServiceCustomLifetime : WindowsServiceLifetime +{ + public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, Settings settings) + : base(environment, applicationLifetime, loggerFactory, optionsAccessor) + { + this.settings = settings; + } + + public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor, Settings settings) + : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) + { + this.settings = settings; + } + + protected override void OnStop() + { + RequestAdditionalTime(settings.ShutdownTimeout); + base.OnStop(); + } + + readonly Settings settings; +} +#pragma warning restore CA1416 \ No newline at end of file From 8952409dad79df07105dd5a9eb44f1692d01766d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 17:29:22 +0100 Subject: [PATCH 23/33] Primary instance custom lifecycle --- .../Hosting/WindowsServiceCustomLifetime.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs diff --git a/src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs b/src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs new file mode 100644 index 0000000000..fb8e28d08b --- /dev/null +++ b/src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs @@ -0,0 +1,32 @@ +namespace ServiceControl.Hosting; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.WindowsServices; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using ServiceBus.Management.Infrastructure.Settings; + +#pragma warning disable CA1416 +class WindowsServiceCustomLifetime : WindowsServiceLifetime +{ + public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, Settings settings) + : base(environment, applicationLifetime, loggerFactory, optionsAccessor) + { + this.settings = settings; + } + + public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor, Settings settings) + : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) + { + this.settings = settings; + } + + protected override void OnStop() + { + RequestAdditionalTime(settings.ShutdownTimeout); + base.OnStop(); + } + + readonly Settings settings; +} +#pragma warning restore CA1416 \ No newline at end of file From 9eb7a8942302d7b15f57b9a8723a70945b289020 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 17:34:08 +0100 Subject: [PATCH 24/33] Add missing using directive --- .../HostApplicationBuilderExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index 90adce523d..ecab39b8f9 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -22,6 +22,7 @@ namespace ServiceControl.Monitoring; using NServiceBus.Features; using NServiceBus.Transport; using QueueLength; +using ServiceControl.Hosting; using Timings; using Transports; From 4cdfc9c1cf29a56229cb1845a34e0ab01a8ab9a3 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 17:36:13 +0100 Subject: [PATCH 25/33] Reword TODO --- src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs index af373056ab..89a2c74feb 100644 --- a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs +++ b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs @@ -13,8 +13,7 @@ sealed class WindowsServiceWithRequestTimeout : WindowsServiceLifetime static readonly TimeSpan CancellationDuration = TimeSpan.FromSeconds(5); readonly HostOptions hostOptions; - // TODO: I don't think this constructor is needed and exist for backwards compability in the runtime - + // TODO: This constructor should not be needed and exist for backwards compability in the runtime // public WindowsServiceWithRequestTimeout(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor) // : this(environment, applicationLifetime, loggerFactory, optionsAccessor, Options.Create(new WindowsServiceLifetimeOptions())) // { From 81dc5809d1bab4fb95595a2633c6f84f3efd444d Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 4 Mar 2025 17:48:26 +0100 Subject: [PATCH 26/33] Remove not needed lifecycles and using directive --- .../HostApplicationBuilderExtensions.cs | 1 - .../Hosting/WindowsServiceCustomLifetime.cs | 31 ------------------ .../Hosting/WindowsServiceCustomLifetime.cs | 32 ------------------- 3 files changed, 64 deletions(-) delete mode 100644 src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs delete mode 100644 src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index ecab39b8f9..90adce523d 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -22,7 +22,6 @@ namespace ServiceControl.Monitoring; using NServiceBus.Features; using NServiceBus.Transport; using QueueLength; -using ServiceControl.Hosting; using Timings; using Transports; diff --git a/src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs b/src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs deleted file mode 100644 index 57f5954dc4..0000000000 --- a/src/ServiceControl.Monitoring/Hosting/WindowsServiceCustomLifetime.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace ServiceControl.Monitoring.Hosting; - -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Hosting.WindowsServices; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -#pragma warning disable CA1416 -class WindowsServiceCustomLifetime : WindowsServiceLifetime -{ - public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, Settings settings) - : base(environment, applicationLifetime, loggerFactory, optionsAccessor) - { - this.settings = settings; - } - - public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor, Settings settings) - : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) - { - this.settings = settings; - } - - protected override void OnStop() - { - RequestAdditionalTime(settings.ShutdownTimeout); - base.OnStop(); - } - - readonly Settings settings; -} -#pragma warning restore CA1416 \ No newline at end of file diff --git a/src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs b/src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs deleted file mode 100644 index fb8e28d08b..0000000000 --- a/src/ServiceControl/Hosting/WindowsServiceCustomLifetime.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace ServiceControl.Hosting; - -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Hosting.WindowsServices; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using ServiceBus.Management.Infrastructure.Settings; - -#pragma warning disable CA1416 -class WindowsServiceCustomLifetime : WindowsServiceLifetime -{ - public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, Settings settings) - : base(environment, applicationLifetime, loggerFactory, optionsAccessor) - { - this.settings = settings; - } - - public WindowsServiceCustomLifetime(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor, Settings settings) - : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) - { - this.settings = settings; - } - - protected override void OnStop() - { - RequestAdditionalTime(settings.ShutdownTimeout); - base.OnStop(); - } - - readonly Settings settings; -} -#pragma warning restore CA1416 \ No newline at end of file From 4772bdb309b4688a90cdcabf74e05e7db07108c9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Mar 2025 15:55:13 +0100 Subject: [PATCH 27/33] Do not try to request additional time on shutdown --- .../WindowsServiceWithRequestTimeout.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs index 89a2c74feb..a6244daebc 100644 --- a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs +++ b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs @@ -30,10 +30,4 @@ protected override void OnStop() RequestAdditionalTime(hostOptions.ShutdownTimeout + CancellationDuration); base.OnStop(); } - - protected override void OnShutdown() - { - RequestAdditionalTime(hostOptions.ShutdownTimeout + CancellationDuration); - base.OnShutdown(); - } } \ No newline at end of file From 49f189d01fd2ee5b5cf8bf506375e3ac2e5ec999 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Mar 2025 17:03:21 +0100 Subject: [PATCH 28/33] Add logging to the WindowsServiceWithRequestTimeout class --- src/ServiceControl.Hosting/ServiceControl.Hosting.csproj | 1 + .../WindowsServiceWithRequestTimeout.cs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj b/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj index 284466ba33..e642344924 100644 --- a/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj +++ b/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj @@ -6,6 +6,7 @@ + diff --git a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs index a6244daebc..c169cc3e9d 100644 --- a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs +++ b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs @@ -27,7 +27,13 @@ public WindowsServiceWithRequestTimeout(IHostEnvironment environment, IHostAppli protected override void OnStop() { - RequestAdditionalTime(hostOptions.ShutdownTimeout + CancellationDuration); + var logger = NLog.LogManager.GetCurrentClassLogger(); + var additionalTime = hostOptions.ShutdownTimeout + CancellationDuration; + + logger.Info($"OnStop invoked, going to ask for additional time: {additionalTime}"); + RequestAdditionalTime(additionalTime); + logger.Info("Additional time requested"); + base.OnStop(); } } \ No newline at end of file From 8ea0e28ec079309d10c9cb72ccc5a66d17d04e62 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Wed, 5 Mar 2025 17:38:48 +0100 Subject: [PATCH 29/33] Use the correct package --- src/ServiceControl.Hosting/ServiceControl.Hosting.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj b/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj index e642344924..074686312c 100644 --- a/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj +++ b/src/ServiceControl.Hosting/ServiceControl.Hosting.csproj @@ -6,7 +6,7 @@ - + From b0fc78616290a218d42490ff49df0de86abc4f40 Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Wed, 5 Mar 2025 19:15:06 +0100 Subject: [PATCH 30/33] Using structured logging --- src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs index c169cc3e9d..6f6117c37d 100644 --- a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs +++ b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs @@ -30,7 +30,7 @@ protected override void OnStop() var logger = NLog.LogManager.GetCurrentClassLogger(); var additionalTime = hostOptions.ShutdownTimeout + CancellationDuration; - logger.Info($"OnStop invoked, going to ask for additional time: {additionalTime}"); + logger.Info("OnStop invoked, going to ask for additional time: {additionalTime}", additionalTime); RequestAdditionalTime(additionalTime); logger.Info("Additional time requested"); From d43b32bc6b22afc9860d97a6fc78055598e9ae7f Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Wed, 5 Mar 2025 19:16:32 +0100 Subject: [PATCH 31/33] Also logging OnShutdown --- .../WindowsServiceWithRequestTimeout.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs index 6f6117c37d..8e7d46e3d3 100644 --- a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs +++ b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs @@ -36,4 +36,11 @@ protected override void OnStop() base.OnStop(); } + + protected override void OnShutdown() + { + var logger = NLog.LogManager.GetCurrentClassLogger(); + logger.Info("OnShutdown invoked, process may exit ungracefully"); + base.OnShutdown(); + } } \ No newline at end of file From f8737dd6df442b4561ebed4a1662d80480afd1b8 Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Wed, 5 Mar 2025 19:17:23 +0100 Subject: [PATCH 32/33] Remove TODO, tested lifetime without constructor on stop and on shutdown --- .../WindowsServiceWithRequestTimeout.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs index 8e7d46e3d3..0e85c8c79b 100644 --- a/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs +++ b/src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs @@ -13,12 +13,6 @@ sealed class WindowsServiceWithRequestTimeout : WindowsServiceLifetime static readonly TimeSpan CancellationDuration = TimeSpan.FromSeconds(5); readonly HostOptions hostOptions; - // TODO: This constructor should not be needed and exist for backwards compability in the runtime - // public WindowsServiceWithRequestTimeout(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor) - // : this(environment, applicationLifetime, loggerFactory, optionsAccessor, Options.Create(new WindowsServiceLifetimeOptions())) - // { - // } - public WindowsServiceWithRequestTimeout(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions optionsAccessor, IOptions windowsServiceOptionsAccessor) : base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor) { From da0f1fe2f53b58843de21cfb80c509cd10802ee9 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 6 Mar 2025 08:46:43 +0100 Subject: [PATCH 33/33] Add the SetupProjectFake project back --- src/ServiceControl.sln | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ServiceControl.sln b/src/ServiceControl.sln index bcd089903c..fa8d9a30e6 100644 --- a/src/ServiceControl.sln +++ b/src/ServiceControl.sln @@ -185,6 +185,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceControl.Transports.P EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceControl.Hosting", "ServiceControl.Hosting\ServiceControl.Hosting.csproj", "{481032A1-1106-4C6C-B75E-512F2FB08882}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetupProcessFake", "SetupProcessFake\SetupProcessFake.csproj", "{5837F789-69B9-44BE-B114-3A2880F06CAB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1011,6 +1013,18 @@ Global {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|x64.Build.0 = Release|Any CPU {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|x86.ActiveCfg = Release|Any CPU {481032A1-1106-4C6C-B75E-512F2FB08882}.Release|x86.Build.0 = Release|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Debug|x64.ActiveCfg = Debug|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Debug|x64.Build.0 = Debug|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Debug|x86.ActiveCfg = Debug|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Debug|x86.Build.0 = Debug|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Release|Any CPU.Build.0 = Release|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Release|x64.ActiveCfg = Release|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Release|x64.Build.0 = Release|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Release|x86.ActiveCfg = Release|Any CPU + {5837F789-69B9-44BE-B114-3A2880F06CAB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1095,6 +1109,7 @@ Global {448CBDCF-718D-4BC7-8F7C-099C9A362B59} = {A21A1A89-0B07-4E87-8E3C-41D9C280DCB8} {18DBEEF5-42EE-4C1D-A05B-87B21C067D53} = {E0E45F22-35E3-4AD8-B09E-EFEA5A2F18EE} {481032A1-1106-4C6C-B75E-512F2FB08882} = {9AF9D3C7-E859-451B-BA4D-B954D289213A} + {5837F789-69B9-44BE-B114-3A2880F06CAB} = {927A078A-E271-4878-A153-86D71AE510E2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3B9E5B72-F580-465A-A22C-2D2148AF4EB4}