diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcServerTransport.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcServerTransport.cs
index 3881ac26e8..9421783908 100644
--- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcServerTransport.cs
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcServerTransport.cs
@@ -14,6 +14,7 @@ namespace Microsoft.Diagnostics.NETCore.Client
{
internal abstract class IpcServerTransport : IDisposable
{
+ private IIpcServerTransportCallbackInternal _callback;
private bool _disposed;
public static IpcServerTransport Create(string transportPath, int maxConnections)
@@ -66,6 +67,16 @@ protected void VerifyNotDisposed()
throw new ObjectDisposedException(this.GetType().Name);
}
}
+
+ internal void SetCallback(IIpcServerTransportCallbackInternal callback)
+ {
+ _callback = callback;
+ }
+
+ protected void OnCreateNewServer()
+ {
+ _callback?.CreatedNewServer();
+ }
}
internal sealed class WindowsPipeServerTransport : IpcServerTransport
@@ -128,6 +139,7 @@ private void CreateNewPipeServer()
_maxInstances,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous);
+ OnCreateNewServer();
}
}
@@ -196,6 +208,12 @@ private void CreateNewSocketServer()
_socket.Bind(_path);
_socket.Listen(_backlog);
_socket.LingerState.Enabled = false;
+ OnCreateNewServer();
}
}
+
+ internal interface IIpcServerTransportCallbackInternal
+ {
+ void CreatedNewServer();
+ }
}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs b/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs
index da5cd66549..548dc49cc4 100644
--- a/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs
+++ b/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs
@@ -171,7 +171,14 @@ private void VerifyIsStarted()
/// A task that completes when the server is no longer listening at the transport path.
private async Task ListenAsync(int maxConnections, CancellationToken token)
{
+ // This disposal shuts down the transport in case an exception is thrown.
using var transport = IpcServerTransport.Create(_transportPath, maxConnections);
+ // Set transport callback for testing purposes.
+ transport.SetCallback(TransportCallback);
+ // This disposal shuts down the transport in case of cancellation; causes the transport
+ // to not recreate the server stream before the AcceptAsync call observes the cancellation.
+ using var _ = token.Register(() => transport.Dispose());
+
while (!token.IsCancellationRequested)
{
Stream stream = null;
@@ -340,5 +347,7 @@ private static bool TestStream(Stream stream)
private bool IsStarted => null != _listenTask;
public static int MaxAllowedConnections = IpcServerTransport.MaxAllowedConnections;
+
+ internal IIpcServerTransportCallbackInternal TransportCallback { get; set; }
}
}
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs
index 5b4250a7b8..96227a222f 100644
--- a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs
@@ -267,6 +267,52 @@ private async Task ReversedServerSingleTargetExitsClientInviableTestCore(bool us
await VerifyNoNewEndpointInfos(server, useAsync);
}
+ ///
+ /// Validates that the does not create a new server
+ /// transport during disposal.
+ ///
+ [Fact]
+ public async Task ReversedServerNoCreateTransportAfterDispose()
+ {
+ var transportCallback = new IpcServerTransportCallback();
+
+ int transportVersion = 0;
+ TestRunner runner = null;
+ try
+ {
+ await using var server = CreateReversedServer(out string transportName);
+ server.TransportCallback = transportCallback;
+ server.Start();
+
+ // Start client pointing to diagnostics server
+ runner = StartTracee(transportName);
+
+ // Get client connection
+ IpcEndpointInfo info = await AcceptEndpointInfo(server, useAsync: true);
+
+ await VerifyEndpointInfo(runner, info, useAsync: true);
+
+ // There should not be any new endpoint infos
+ await VerifyNoNewEndpointInfos(server, useAsync: true);
+
+ ResumeRuntime(info);
+
+ await VerifyWaitForConnection(info, useAsync: true);
+
+ transportVersion = await transportCallback.GetStableTransportVersion();
+
+ // Server will be disposed
+ }
+ finally
+ {
+ _outputHelper.WriteLine("Stopping tracee.");
+ runner?.Stop();
+ }
+
+ // Check that the reversed server did not create a new server transport upon disposal.
+ Assert.Equal(transportVersion, await transportCallback.GetStableTransportVersion());
+ }
+
private ReversedDiagnosticsServer CreateReversedServer(out string transportName)
{
transportName = ReversedServerHelper.CreateServerTransportName();
@@ -592,5 +638,84 @@ public async Task WaitForConnection(TimeSpan timeout, bool expectTimeout = false
}
}
}
+
+ private class IpcServerTransportCallback : IIpcServerTransportCallbackInternal
+ {
+ private static readonly TimeSpan StableTransportSemaphoreTimeout = TimeSpan.FromSeconds(3);
+ private static readonly TimeSpan StableTransportVersionPeriod = TimeSpan.FromSeconds(3);
+ private static readonly TimeSpan StableTransportVersionTimeout = TimeSpan.FromSeconds(30);
+
+ private readonly Timer _transportVersionTimer;
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
+
+ private int _transportVersion = 0;
+ private TaskCompletionSource _transportVersionSource;
+
+ public IpcServerTransportCallback()
+ {
+ // Initially set timer to not callback
+ _transportVersionTimer = new Timer(NotifyStableTransportVersion, this, Timeout.Infinite, 0);
+ }
+
+ public void CreatedNewServer()
+ {
+ _semaphore.Wait(StableTransportSemaphoreTimeout);
+ try
+ {
+ _transportVersion++;
+ // Restart timer with existing settings
+ _transportVersionTimer.Change(0, 0);
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ private static void NotifyStableTransportVersion(object state)
+ {
+ ((IpcServerTransportCallback)state).NotifyStableTransportVersion();
+ }
+
+ private void NotifyStableTransportVersion()
+ {
+ _semaphore.Wait(StableTransportSemaphoreTimeout);
+ try
+ {
+ // Disable timer callback
+ _transportVersionTimer.Change(Timeout.Infinite, 0);
+ // Notify and clear the completion source
+ _transportVersionSource?.TrySetResult(_transportVersion);
+ _transportVersionSource = null;
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+
+ public async Task GetStableTransportVersion()
+ {
+ await _semaphore.WaitAsync(StableTransportSemaphoreTimeout);
+ try
+ {
+ _transportVersionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ // Set timer to callback a period of time after last transport update
+ _transportVersionTimer.Change(StableTransportVersionPeriod, Timeout.InfiniteTimeSpan);
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+
+ using var cancellation = new CancellationTokenSource(StableTransportVersionTimeout);
+
+ CancellationToken token = cancellation.Token;
+ using var _ = token.Register(() => _transportVersionSource.TrySetCanceled(token));
+
+ // Wait for the transport version to stabilize for a certain amount of time.
+ return await _transportVersionSource.Task;
+ }
+ }
}
}