Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d83e05d
Introduce a setting to control the primary instance shutdown timeout
mauroservienti Feb 18, 2025
0f21c3d
Introduce a setting to control the audit instance shutdown timeout
mauroservienti Feb 18, 2025
34cfc44
Approved API
mauroservienti Feb 18, 2025
63b33ac
Set the default shutdown timeout to the max allowed by the most restr…
mauroservienti Feb 18, 2025
1305f0f
When running the installer engine (via SCMU or PowerShell) we can ass…
mauroservienti Feb 18, 2025
0a93c9e
Ensure the ShutdownTimeout is set to 2 minutes when installing/updati…
mauroservienti Mar 4, 2025
6bbca4a
Update src/ServiceControl/Infrastructure/Settings/Settings.cs
mauroservienti Mar 4, 2025
c401f38
Set the default shutdown timeout to 5 seconds
mauroservienti Mar 4, 2025
7e58847
Add the required version
mauroservienti Mar 4, 2025
135e9ef
Fix approved files adding ShutdownTimeout
mauroservienti Mar 4, 2025
eb438d8
Add the ShutdownTimeout to the audit instance settings
mauroservienti Mar 4, 2025
8eb5b48
Add the ShutdownTimeout to the Monitoring instance
mauroservienti Mar 4, 2025
7c0a3e0
Fix the settings namespace
mauroservienti Mar 4, 2025
93de0ac
Approval files
mauroservienti Mar 4, 2025
780acff
Set the SemanticVersion for the ShutdownTimeout to 6.4.1
mauroservienti Mar 4, 2025
a8292eb
Surround AddWindowsService() with a check to validate it's running as…
mauroservienti Mar 4, 2025
0f4c097
Add the WindowsServiceCustomLifetime to request additional time on st…
mauroservienti Mar 4, 2025
c29fa13
Suppress CA1416
mauroservienti Mar 4, 2025
34d12c8
Extracted into separate project, support OnShutdown, using `Supported…
ramonsmits Mar 4, 2025
825ead5
fixup! Extracted into separate project, support OnShutdown, using `Su…
ramonsmits Mar 4, 2025
4a9ead7
Forgot MaintenanceModeCommand
ramonsmits Mar 4, 2025
a662a73
Add the monitoring instance custom lifecycle to request additional ti…
mauroservienti Mar 4, 2025
8952409
Primary instance custom lifecycle
mauroservienti Mar 4, 2025
9eb7a89
Add missing using directive
mauroservienti Mar 4, 2025
4cdfc9c
Reword TODO
mauroservienti Mar 4, 2025
81dc580
Remove not needed lifecycles and using directive
mauroservienti Mar 4, 2025
4772bdb
Do not try to request additional time on shutdown
mauroservienti Mar 5, 2025
49f189d
Add logging to the WindowsServiceWithRequestTimeout class
mauroservienti Mar 5, 2025
8ea0e28
Use the correct package
mauroservienti Mar 5, 2025
b0fc786
Using structured logging
ramonsmits Mar 5, 2025
d43b32b
Also logging OnShutdown
ramonsmits Mar 5, 2025
f8737dd
Remove TODO, tested lifetime without constructor on stop and on shutdown
ramonsmits Mar 5, 2025
da0f1fe
Add the SetupProjectFake project back
mauroservienti Mar 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
"MaximumConcurrencyLevel": null,
"ServiceControlQueueAddress": "Particular.ServiceControl",
"TimeToRestartAuditIngestionAfterFailure": "00:01:00",
"EnableFullTextSearchOnBodies": true
"EnableFullTextSearchOnBodies": true,
"ShutdownTimeout": "00:00:05"
}
10 changes: 8 additions & 2 deletions src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ namespace ServiceControl.Audit;
using System.Threading;
using System.Threading.Tasks;
using Auditing;
using Hosting;
using Infrastructure;
using Infrastructure.Settings;
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;
Expand Down Expand Up @@ -45,7 +47,7 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder,
var transportCustomization = TransportFactory.Create(transportSettings);
transportCustomization.AddTransportForAudit(services, transportSettings);

services.Configure<HostOptions>(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30));
services.Configure<HostOptions>(options => options.ShutdownTimeout = settings.ShutdownTimeout);

services.AddSingleton(settings);
services.AddSingleton<EndpointInstanceMonitoring>();
Expand Down Expand Up @@ -100,7 +102,11 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder,
services.AddHostedService<AuditIngestion>();
}

builder.Services.AddWindowsService();
if (WindowsServiceHelpers.IsWindowsService())
{
// The if is added for clarity, internally AddWindowsService has a similar logic
builder.AddWindowsServiceWithRequestTimeout();
}
}

public static void AddServiceControlAuditInstallers(this IHostApplicationBuilder builder, Settings settings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
{
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices;
using Persistence;
using ServiceControl.Hosting;
using Settings;

class MaintenanceModeCommand : AbstractCommand
Expand All @@ -17,7 +19,11 @@ 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.AddWindowsServiceWithRequestTimeout();
}

var host = hostBuilder.Build();
await host.RunAsync();
Expand Down
7 changes: 7 additions & 0 deletions src/ServiceControl.Audit/Infrastructure/Settings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public Settings(string transportType = null, string persisterType = null, Loggin
ServiceControlQueueAddress = SettingsReader.Read<string>(SettingsRootNamespace, "ServiceControlQueueAddress");
TimeToRestartAuditIngestionAfterFailure = GetTimeToRestartAuditIngestionAfterFailure();
EnableFullTextSearchOnBodies = SettingsReader.Read(SettingsRootNamespace, "EnableFullTextSearchOnBodies", true);
ShutdownTimeout = SettingsReader.Read(SettingsRootNamespace, "ShutdownTimeout", ShutdownTimeout);

AssemblyLoadContextResolver = static assemblyPath => new PluginAssemblyLoadContext(assemblyPath);
}
Expand Down Expand Up @@ -152,6 +153,12 @@ public int MaxBodySizeToStore

public bool EnableFullTextSearchOnBodies { 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
Expand Down
1 change: 1 addition & 0 deletions src/ServiceControl.Audit/ServiceControl.Audit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ProjectReference Include="..\ServiceControl.Infrastructure\ServiceControl.Infrastructure.csproj" />
<ProjectReference Include="..\ServiceControl.LicenseManagement\ServiceControl.LicenseManagement.csproj" />
<ProjectReference Include="..\ServiceControl.Transports\ServiceControl.Transports.csproj" />
<ProjectReference Include="..\ServiceControl.Hosting\ServiceControl.Hosting.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
17 changes: 17 additions & 0 deletions src/ServiceControl.Hosting/IHostApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<IHostLifetime, WindowsServiceWithRequestTimeout>();
}
}
}
12 changes: 12 additions & 0 deletions src/ServiceControl.Hosting/ServiceControl.Hosting.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" />
<PackageReference Include="NLog.Extensions.Logging" />
</ItemGroup>

</Project>
40 changes: 40 additions & 0 deletions src/ServiceControl.Hosting/WindowsServiceWithRequestTimeout.cs
Original file line number Diff line number Diff line change
@@ -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;

public WindowsServiceWithRequestTimeout(IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions<HostOptions> optionsAccessor, IOptions<WindowsServiceLifetimeOptions> windowsServiceOptionsAccessor)
: base(environment, applicationLifetime, loggerFactory, optionsAccessor, windowsServiceOptionsAccessor)
{
hostOptions = optionsAccessor.Value;
}

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}", additionalTime);
RequestAdditionalTime(additionalTime);
logger.Info("Additional time requested");

base.OnStop();
}

protected override void OnShutdown()
{
var logger = NLog.LogManager.GetCurrentClassLogger();
logger.Info("OnShutdown invoked, process may exit ungracefully");
base.OnShutdown();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"EndpointUptimeGracePeriod": "00:00:40",
"RootUrl": "http://localhost:9999/",
"MaximumConcurrencyLevel": null,
"ServiceControlThroughputDataQueue": "ServiceControl.ThroughputData"
"ServiceControlThroughputDataQueue": "ServiceControl.ThroughputData",
"ShutdownTimeout": "00:00:05"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,6 +14,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;
Expand All @@ -39,7 +41,13 @@ public static void AddServiceControlMonitoring(this IHostApplicationBuilder host
var transportCustomization = TransportFactory.Create(transportSettings);
transportCustomization.AddTransportForMonitoring(services, transportSettings);

services.AddWindowsService();
services.Configure<HostOptions>(options => options.ShutdownTimeout = settings.ShutdownTimeout);

if (WindowsServiceHelpers.IsWindowsService())
{
// The if is added for clarity, internally AddWindowsService has a similar logic
hostBuilder.AddWindowsServiceWithRequestTimeout();
}

services.AddSingleton(settings);
services.AddSingleton<EndpointRegistry>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<ProjectReference Include="..\ServiceControl.Infrastructure\ServiceControl.Infrastructure.csproj" />
<ProjectReference Include="..\ServiceControl.LicenseManagement\ServiceControl.LicenseManagement.csproj" />
<ProjectReference Include="..\ServiceControl.Transports\ServiceControl.Transports.csproj" />
<ProjectReference Include="..\ServiceControl.Hosting\ServiceControl.Hosting.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 7 additions & 0 deletions src/ServiceControl.Monitoring/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int?>(SettingsRootNamespace, "MaximumConcurrencyLevel");
ServiceControlThroughputDataQueue = SettingsReader.Read(SettingsRootNamespace, "ServiceControlThroughputDataQueue", "ServiceControl.ThroughputData");
ShutdownTimeout = SettingsReader.Read(SettingsRootNamespace, "ShutdownTimeout", ShutdownTimeout);

AssemblyLoadContextResolver = static assemblyPath => new PluginAssemblyLoadContext(assemblyPath);
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
"MaximumConcurrencyLevel": null,
"RetryHistoryDepth": 10,
"RemoteInstances": [],
"DisableHealthChecks": false
"DisableHealthChecks": false,
"ShutdownTimeout": "00:00:05"
}
43 changes: 29 additions & 14 deletions src/ServiceControl.sln
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ 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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetupProcessFake", "SetupProcessFake\SetupProcessFake.csproj", "{5837F789-69B9-44BE-B114-3A2880F06CAB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -999,18 +1001,30 @@ 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
{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
Expand Down Expand Up @@ -1094,7 +1108,8 @@ 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}
{5837F789-69B9-44BE-B114-3A2880F06CAB} = {927A078A-E271-4878-A153-86D71AE510E2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3B9E5B72-F580-465A-A22C-2D2148AF4EB4}
Expand Down
10 changes: 8 additions & 2 deletions src/ServiceControl/HostApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +18,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;
Expand Down Expand Up @@ -51,7 +53,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S
var transportCustomization = TransportFactory.Create(transportSettings);
transportCustomization.AddTransportForPrimary(services, transportSettings);

services.Configure<HostOptions>(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30));
services.Configure<HostOptions>(options => options.ShutdownTimeout = settings.ShutdownTimeout);
services.AddSingleton<IDomainEvents, DomainEvents>();

services.AddSingleton<MessageStreamerHub>();
Expand Down Expand Up @@ -95,7 +97,11 @@ 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.AddWindowsServiceWithRequestTimeout();
}

hostBuilder.AddServiceControlComponents(settings, transportCustomization, ServiceControlMainInstance.Components);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,7 +14,11 @@ 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.AddWindowsServiceWithRequestTimeout();
}

var host = hostBuilder.Build();
await host.RunAsync();
Expand Down
7 changes: 7 additions & 0 deletions src/ServiceControl/Infrastructure/Settings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -180,6 +181,12 @@ 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 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()
{
var settingsValue = SettingsReader.Read<string>(SettingsRootNamespace, "ConnectionString");
Expand Down
Loading