Skip to content

Commit

Permalink
Implement ServiceController.Stop(bool) overload (#52519)
Browse files Browse the repository at this point in the history
* Implement ServiceController.Stop(bool) overload with test

* Add basic docs for new ServiceController.Stop overload

* Name boolean parameter

Co-authored-by: Ilya <darpa@yandex.ru>

* Add a test for a valid use of ServiceController.Stop(bool)

* Run tests for the new Stop(bool) overload only on .NETCoreApp

* Add a test for manually stopping a service and its dependents with Stop(false)

* Expose new Stop(bool) overload only for .NET Core 3.1+

* Target netcoreapp3.1

Co-authored-by: Ilya <darpa@yandex.ru>
Co-authored-by: Viktor Hofer <viktor.hofer@microsoft.com>
  • Loading branch information
3 people authored Jun 15, 2021
1 parent dfcaf41 commit ec27129
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;net461</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent);netcoreapp3.1;netstandard2.0;net461</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Include="System.ServiceProcess.ServiceController.cs" Condition="'$(TargetFramework)' != 'net461'" />
<Compile Include="System.ServiceProcess.ServiceController.net461.cs" Condition="'$(TargetFramework)' == 'net461'" />
<Compile Include="System.ServiceProcess.ServiceController.netcoreapp.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)System.Diagnostics.EventLog\ref\System.Diagnostics.EventLog.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<Reference Include="System.Runtime" />
<Reference Include="System.ComponentModel.TypeConverter" />
<Reference Include="System.ComponentModel.Primitives" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<Reference Include="System.ServiceProcess" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// ------------------------------------------------------------------------------
// Changes to this file must follow the https://aka.ms/api-review process.
// ------------------------------------------------------------------------------

namespace System.ServiceProcess
{
public partial class ServiceController : System.ComponentModel.Component
{
public void Stop(bool stopDependentServices) { }
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netstandard2.0-windows;netstandard2.0;net461-windows</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netcoreapp3.1-windows;netcoreapp3.1;netstandard2.0-windows;netstandard2.0;net461-windows</TargetFrameworks>
<NoWarn>$(NoWarn);CA2249</NoWarn>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down Expand Up @@ -89,6 +89,9 @@
<Reference Include="System.Threading.Thread" />
<Reference Include="System.Threading.ThreadPool" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('netcoreapp3.1'))">
<Reference Include="System.ComponentModel.TypeConverter" />
</ItemGroup>
<ItemGroup Condition="'$(IsPartialFacadeAssembly)' == 'true'">
<Reference Include="System.ServiceProcess" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -912,19 +912,42 @@ public void Start(string[] args)
/// they will be stopped first. The DependentServices property lists this set
/// of services.
/// </summary>
public unsafe void Stop()
public void Stop()
{
Stop(stopDependentServices: true);
}

/// <summary>
/// Stops the service and optionally any services that are dependent on this service.
/// </summary>
/// <remarks>
/// If any other services depend on this one, you need to either pass <c>true</c> for
/// <paramref name="stopDependentServices"/> or stop them manually before calling this method.
/// </remarks>
/// <param name="stopDependentServices">
/// <c>true</c> to stop all running dependent services together with the service; <c>false</c> to stop only the service.
/// </param>
#if NETCOREAPP3_1_OR_GREATER
public
#else
private
#endif
unsafe void Stop(bool stopDependentServices)
{
using SafeServiceHandle serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_STOP);
// Before stopping this service, stop all the dependent services that are running.
// (It's OK not to cache the result of getting the DependentServices property because it caches on its own.)
for (int i = 0; i < DependentServices.Length; i++)
if (stopDependentServices)
{
ServiceController currentDependent = DependentServices[i];
currentDependent.Refresh();
if (currentDependent.Status != ServiceControllerStatus.Stopped)
// Before stopping this service, stop all the dependent services that are running.
// (It's OK not to cache the result of getting the DependentServices property because it caches on its own.)
for (int i = 0; i < DependentServices.Length; i++)
{
currentDependent.Stop();
currentDependent.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 30));
ServiceController currentDependent = DependentServices[i];
currentDependent.Refresh();
if (currentDependent.Status != ServiceControllerStatus.Stopped)
{
currentDependent.Stop();
currentDependent.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 30));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace System.ServiceProcess.Tests
{
[OuterLoop(/* Modifies machine state */)]
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Persistent issues starting test service on NETFX")]
public class ServiceControllerTests : IDisposable
public partial class ServiceControllerTests : IDisposable
{
private const int connectionTimeout = 30000;
private readonly TestServiceProvider _testService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Xunit;

namespace System.ServiceProcess.Tests
{
[OuterLoop(/* Modifies machine state */)]
public partial class ServiceControllerTests : IDisposable
{
[ConditionalFact(nameof(IsProcessElevated))]
public void Stop_FalseArg_WithDependentServices_ThrowsInvalidOperationException()
{
var controller = new ServiceController(_testService.TestServiceName);
controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout);
Assert.Throws<InvalidOperationException>(() => controller.Stop(stopDependentServices: false));
}

[ConditionalFact(nameof(IsProcessElevated))]
public void Stop_TrueArg_WithDependentServices_StopsTheServiceAndItsDependents()
{
var controller = new ServiceController(_testService.TestServiceName);
controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout);

controller.Stop(stopDependentServices: true);
controller.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout);

Assert.Equal(ServiceControllerStatus.Stopped, controller.Status);
Assert.All(controller.DependentServices, service => Assert.Equal(ServiceControllerStatus.Stopped, service.Status));
}

[ConditionalFact(nameof(IsProcessElevated))]
public void StopTheServiceAndItsDependentsManually()
{
var controller = new ServiceController(_testService.TestServiceName);
controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout);

foreach (var dependentService in controller.DependentServices)
{
dependentService.Stop(stopDependentServices: false);
dependentService.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout);
}
controller.Stop(stopDependentServices: false);
controller.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout);

Assert.Equal(ServiceControllerStatus.Stopped, controller.Status);
Assert.All(controller.DependentServices, service => Assert.Equal(ServiceControllerStatus.Stopped, service.Status));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<Compile Include="ServiceControllerTests.cs" />
<Compile Include="ServiceProcessDescriptionAttributeTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<Compile Include="ServiceControllerTests.netcoreapp.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="System.ServiceProcess.ServiceController.TestService\System.ServiceProcess.ServiceController.TestService.csproj" />
</ItemGroup>
Expand Down

0 comments on commit ec27129

Please sign in to comment.