diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs
index 7ba222918425c1..00a392a2bec071 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs
@@ -305,6 +305,8 @@ internal static bool ValidServiceName(string serviceName)
///
/// Disposes of the resources (other than memory ) used by
/// the .
+ /// This is called from when all
+ /// services in the process have entered the SERVICE_STOPPED state.
///
protected override void Dispose(bool disposing)
{
diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs
index efe9733aa51100..da535fafea8e79 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceBaseTests.cs
@@ -84,17 +84,21 @@ public void TestOnStartThenStop()
public void TestOnStartWithArgsThenStop()
{
ServiceController controller = ConnectToServer();
-
controller.Stop();
Assert.Equal((int)PipeMessageByteCode.Stop, _testService.GetByte());
controller.WaitForStatus(ServiceControllerStatus.Stopped);
controller.Start(new string[] { "StartWithArguments", "a", "b", "c" });
+
+ // Start created a new TestService; dispose of our client stream and reconnect to it
_testService.Client = null;
_testService.Client.Connect();
- Assert.Equal((int)(PipeMessageByteCode.Connected), _testService.GetByte());
- Assert.Equal((int)(PipeMessageByteCode.Start), _testService.GetByte());
+ // Test service does not mutually synchronize Connected and Start messages
+ var bytes = new byte[] { _testService.GetByte(), _testService.GetByte() };
+ Assert.Contains((byte)PipeMessageByteCode.Connected, bytes);
+ Assert.Contains((byte)PipeMessageByteCode.Start, bytes);
+
controller.WaitForStatus(ServiceControllerStatus.Running);
controller.Stop();
@@ -202,8 +206,10 @@ public void PropagateExceptionFromOnStart()
private ServiceController ConnectToServer()
{
+ TestServiceProvider.DebugTrace("ServiceBaseTests.ConnectToServer: connecting");
_testService.Client.Connect(connectionTimeout);
Assert.Equal((int)PipeMessageByteCode.Connected, _testService.GetByte());
+ TestServiceProvider.DebugTrace("ServiceBaseTests.ConnectToServer: received connect byte");
ServiceController controller = new ServiceController(_testService.TestServiceName);
AssertExpectedProperties(controller);
diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs
index 8773e4e3acfbeb..abff7d1c7c7425 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/Program.cs
@@ -1,12 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Threading;
+
namespace System.ServiceProcess.Tests
{
public class Program
{
static int Main(string[] args)
{
+ Thread.CurrentThread.Name = "Test Service";
if (args.Length == 1 || args.Length == 2)
{
TestService testService;
diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs
index a22f7b967594f6..5d3d9bc808f704 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestService.cs
@@ -9,6 +9,11 @@ namespace System.ServiceProcess.Tests
{
public class TestService : ServiceBase
{
+ // To view tracing, use DbgView from sysinternals.com;
+ // run it elevated, check "Capture>Global Win32" and "Capture>Win32",
+ // and filter to just messages beginning with "##"
+ internal const bool DebugTracing = false;
+
private bool _disposed;
private Task _waitClientConnect;
private NamedPipeServerStream _serverStream;
@@ -16,6 +21,7 @@ public class TestService : ServiceBase
public TestService(string serviceName, Exception throwException = null)
{
+ DebugTrace("TestService " + ServiceName + ": Ctor");
this.ServiceName = serviceName;
// Enable all the events
@@ -27,10 +33,11 @@ public TestService(string serviceName, Exception throwException = null)
this.CanHandleSessionChangeEvent = false;
this.CanHandlePowerEvent = false;
this._exception = throwException;
-
this._serverStream = new NamedPipeServerStream(serviceName);
_waitClientConnect = this._serverStream.WaitForConnectionAsync();
- _waitClientConnect.ContinueWith((t) => WriteStreamAsync(PipeMessageByteCode.Connected));
+ _waitClientConnect = _waitClientConnect.ContinueWith(_ => DebugTrace("TestService " + ServiceName + ": Connected"));
+ _waitClientConnect.ContinueWith(t => WriteStreamAsync(PipeMessageByteCode.Connected));
+ DebugTrace("TestService " + ServiceName + ": Ctor completed");
}
protected override void OnContinue()
@@ -72,6 +79,7 @@ protected override void OnShutdown()
protected override void OnStart(string[] args)
{
+ DebugTrace("TestService " + ServiceName + ": OnStart");
base.OnStart(args);
if (_exception != null)
{
@@ -89,41 +97,52 @@ protected override void OnStart(string[] args)
protected override void OnStop()
{
+ DebugTrace("TestService " + ServiceName + ": OnStop");
base.OnStop();
WriteStreamAsync(PipeMessageByteCode.Stop).Wait();
}
public async Task WriteStreamAsync(PipeMessageByteCode code, int command = 0)
{
- if (_waitClientConnect.IsCompleted)
- {
- Task writeCompleted;
- const int WriteTimeout = 60000;
- if (code == PipeMessageByteCode.OnCustomCommand)
- {
- writeCompleted = _serverStream.WriteAsync(new byte[] { (byte)command }, 0, 1);
- }
- else
- {
- writeCompleted = _serverStream.WriteAsync(new byte[] { (byte)code }, 0, 1);
- }
- await writeCompleted.TimeoutAfter(WriteTimeout).ConfigureAwait(false);
- }
- else
+ DebugTrace("TestService " + ServiceName + ": WriteStreamAsync writing " + code.ToString());
+
+ const int WriteTimeout = 60000;
+ var toWrite = (code == PipeMessageByteCode.OnCustomCommand) ? new byte[] { (byte)command } : new byte[] { (byte)code };
+
+ // Wait for the client connection before writing to the pipe.
+ // Exception: if it's a Stop message, it may be because the test has completed and we're cleaning up the test service so there's no client at all.
+ // Tests that verify "Stop" itself should ensure the client connection has completed before calling stop, by waiting on some other message from the pipe first.
+ if (code != PipeMessageByteCode.Stop)
{
- // We get here if the service is getting torn down before a client ever connected.
- // some tests do this.
+ await _waitClientConnect;
}
+ await _serverStream.WriteAsync(toWrite, 0, 1).TimeoutAfter(WriteTimeout).ConfigureAwait(false);
+ DebugTrace("TestService " + ServiceName + ": WriteStreamAsync completed");
}
+ ///
+ /// This is called from when all services in the process
+ /// have entered the SERVICE_STOPPED state. It disposes the named pipe stream.
+ ///
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
+ DebugTrace("TestService " + ServiceName + ": Disposing");
_serverStream.Dispose();
_disposed = true;
base.Dispose();
}
}
+
+ internal static void DebugTrace(string message)
+ {
+ if (DebugTracing)
+ {
+#pragma warning disable CS0162 // unreachable code
+ Debug.WriteLine("## " + message);
+#pragma warning restore
+ }
+ }
}
}
diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs
index 10d941807aec8f..daad7d2af0ac23 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs
@@ -102,6 +102,7 @@ public unsafe void Install()
{
if (svc.Status != ServiceControllerStatus.Running)
{
+ TestService.DebugTrace("TestServiceInstaller: instructing ServiceController to Start service " + ServiceName);
svc.Start();
if (!ServiceName.StartsWith("PropagateExceptionFromOnStart"))
svc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(120));
@@ -124,7 +125,6 @@ public void RemoveService()
// Meantime we still want this service to get deleted, so we'll go ahead and call
// DeleteService, which will schedule it to get deleted on reboot.
// We won't catch the exception: we do want the test to fail.
-
DeleteService();
ServiceName = null;
@@ -141,6 +141,7 @@ private void StopService()
{
try
{
+ TestService.DebugTrace("TestServiceInstaller: instructing ServiceController to Stop service " + ServiceName);
svc.Stop();
}
catch (InvalidOperationException)
@@ -172,6 +173,7 @@ private void DeleteService()
if (serviceHandle.IsInvalid)
throw new Win32Exception($"Could not find service '{ServiceName}'");
+ TestService.DebugTrace("TestServiceInstaller: instructing ServiceController to Delete service " + ServiceName);
if (!Interop.Advapi32.DeleteService(serviceHandle))
{
throw new Win32Exception($"Could not delete service '{ServiceName}'");
diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs
index c875d7ea895016..1be86a61785dc9 100644
--- a/src/libraries/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs
+++ b/src/libraries/System.ServiceProcess.ServiceController/tests/TestServiceProvider.cs
@@ -10,6 +10,11 @@ namespace System.ServiceProcess.Tests
{
internal sealed class TestServiceProvider
{
+ // To view tracing, use DbgView from sysinternals.com;
+ // run it elevated, check "Capture>Global Win32" and "Capture>Win32",
+ // and filter to just messages beginning with "##"
+ internal const bool DebugTracing = false;
+
private const int readTimeout = 60000;
private static readonly Lazy s_runningWithElevatedPrivileges = new Lazy(
@@ -28,6 +33,7 @@ public NamedPipeClientStream Client
{
if (_client == null)
{
+ DebugTrace("TestServiceProvider: Creating client stream");
_client = new NamedPipeClientStream(".", TestServiceName, PipeDirection.In);
}
return _client;
@@ -36,10 +42,12 @@ public NamedPipeClientStream Client
{
if (value == null)
{
+ DebugTrace("TestServiceProvider: Disposing client stream");
_client.Dispose();
_client = null;
}
}
+
}
public readonly string TestServiceAssembly = typeof(TestService).Assembly.Location;
@@ -125,6 +133,7 @@ public void DeleteTestServices()
{
if (_client != null)
{
+ DebugTrace("TestServiceProvider: Disposing client stream in Dispose()");
_client.Dispose();
_client = null;
}
@@ -143,5 +152,15 @@ public void DeleteTestServices()
}
}
}
+
+ internal static void DebugTrace(string message)
+ {
+ if (DebugTracing)
+ {
+#pragma warning disable CS0162 // unreachable code
+ Debug.WriteLine("## " + message);
+#pragma warning restore
+ }
+ }
}
}