diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 4a9d818015..4ea7cdee98 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -4,9 +4,9 @@
https://github.com/dotnet/command-line-api
166610c56ff732093f0145a2911d4f6c40b786da
-
+
https://github.com/dotnet/arcade
- ecec08a0eebbd92bb9538e351d475582551d9092
+ 83fda4b3f6d93e713749fd1b27c4a6d40b118b13
https://github.com/dotnet/symstore
@@ -14,9 +14,9 @@
-
+
https://github.com/dotnet/arcade
- ecec08a0eebbd92bb9538e351d475582551d9092
+ 83fda4b3f6d93e713749fd1b27c4a6d40b118b13
https://github.com/dotnet/runtime
diff --git a/eng/Versions.props b/eng/Versions.props
index 1aa6fc7b87..5a4aa503b6 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -54,7 +54,7 @@
4.7.0
2.4.1
2.0.3
- 5.0.0-beta.20411.8
+ 5.0.0-beta.20417.6
10.0.18362
diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake
index 2566707997..137736c0a2 100644
--- a/eng/common/cross/toolchain.cmake
+++ b/eng/common/cross/toolchain.cmake
@@ -19,7 +19,9 @@ if(TARGET_ARCH_NAME STREQUAL "armel")
endif()
elseif(TARGET_ARCH_NAME STREQUAL "arm")
set(CMAKE_SYSTEM_PROCESSOR armv7l)
- if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf)
+ if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv7-alpine-linux-musleabihf)
+ set(TOOLCHAIN "armv7-alpine-linux-musleabihf")
+ elseif(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf)
set(TOOLCHAIN "armv6-alpine-linux-musleabihf")
else()
set(TOOLCHAIN "arm-linux-gnueabihf")
diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml
index 0e79a546b5..e814d5e033 100644
--- a/eng/common/templates/post-build/post-build.yml
+++ b/eng/common/templates/post-build/post-build.yml
@@ -39,6 +39,7 @@ parameters:
NetEngLatestChannelId: 2
NetEngValidationChannelId: 9
NetDev5ChannelId: 131
+ NetDev6ChannelId: 1296
GeneralTestingChannelId: 529
NETCoreToolingDevChannelId: 548
NETCoreToolingReleaseChannelId: 549
@@ -46,7 +47,6 @@ parameters:
NETCoreExperimentalChannelId: 562
NetEngServicesIntChannelId: 678
NetEngServicesProdChannelId: 679
- Net5Preview7ChannelId: 1065
Net5Preview8ChannelId: 1155
Net5RC1ChannelId: 1157
NetCoreSDK313xxChannelId: 759
@@ -115,7 +115,7 @@ stages:
inputs:
filePath: $(Build.SourcesDirectory)/eng/common/post-build/check-channel-consistency.ps1
arguments: -PromoteToChannels "$(TargetChannels)"
- -AvailableChannelIds ${{parameters.NetEngLatestChannelId}},${{parameters.NetEngValidationChannelId}},${{parameters.NetDev5ChannelId}},${{parameters.GeneralTestingChannelId}},${{parameters.NETCoreToolingDevChannelId}},${{parameters.NETCoreToolingReleaseChannelId}},${{parameters.NETInternalToolingChannelId}},${{parameters.NETCoreExperimentalChannelId}},${{parameters.NetEngServicesIntChannelId}},${{parameters.NetEngServicesProdChannelId}},${{parameters.Net5Preview7ChannelId}},${{parameters.Net5Preview8ChannelId}},${{parameters.Net5RC1ChannelId}},${{parameters.NetCoreSDK313xxChannelId}},${{parameters.NetCoreSDK313xxInternalChannelId}},${{parameters.NetCoreSDK314xxChannelId}},${{parameters.NetCoreSDK314xxInternalChannelId}},${{parameters.VS166ChannelId}},${{parameters.VS167ChannelId}},${{parameters.VS168ChannelId}},${{parameters.VSMasterChannelId}}
+ -AvailableChannelIds ${{parameters.NetEngLatestChannelId}},${{parameters.NetEngValidationChannelId}},${{parameters.NetDev5ChannelId}},${{parameters.NetDev6ChannelId}},${{parameters.GeneralTestingChannelId}},${{parameters.NETCoreToolingDevChannelId}},${{parameters.NETCoreToolingReleaseChannelId}},${{parameters.NETInternalToolingChannelId}},${{parameters.NETCoreExperimentalChannelId}},${{parameters.NetEngServicesIntChannelId}},${{parameters.NetEngServicesProdChannelId}},${{parameters.Net5Preview8ChannelId}},${{parameters.Net5RC1ChannelId}},${{parameters.NetCoreSDK313xxChannelId}},${{parameters.NetCoreSDK313xxInternalChannelId}},${{parameters.NetCoreSDK314xxChannelId}},${{parameters.NetCoreSDK314xxInternalChannelId}},${{parameters.VS166ChannelId}},${{parameters.VS167ChannelId}},${{parameters.VS168ChannelId}},${{parameters.VSMasterChannelId}}
- job:
displayName: NuGet Validation
@@ -276,13 +276,13 @@ stages:
dependsOn: ${{ parameters.publishDependsOn }}
publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }}
symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }}
- stageName: 'Net5_Preview7_Publish'
- channelName: '.NET 5 Preview 7'
- akaMSChannelName: 'net5/preview7'
- channelId: ${{ parameters.Net5Preview7ChannelId }}
- transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json'
- shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json'
- symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json'
+ stageName: 'NetCore_Dev6_Publish'
+ channelName: '.NET 6 Dev'
+ akaMSChannelName: 'net6/dev'
+ channelId: ${{ parameters.NetDev6ChannelId }}
+ transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6-transport/nuget/v3/index.json'
+ shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json'
+ symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6-symbols/nuget/v3/index.json'
- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml
parameters:
@@ -298,7 +298,7 @@ stages:
shippingFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal/nuget/v3/index.json'
symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal-symbols/nuget/v3/index.json'
- - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml
+ - template: \eng\common\templates\post-build\channels\generic-public-channel.yml
parameters:
artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }}
dependsOn: ${{ parameters.publishDependsOn }}
@@ -308,9 +308,9 @@ stages:
channelName: '.NET 5 RC 1'
akaMSChannelName: 'net5/rc1'
channelId: ${{ parameters.Net5RC1ChannelId }}
- transportFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal-transport/nuget/v3/index.json'
- shippingFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal/nuget/v3/index.json'
- symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal-symbols/nuget/v3/index.json'
+ transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json'
+ shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json'
+ symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json'
- template: \eng\common\templates\post-build\channels\generic-public-channel.yml
parameters:
diff --git a/global.json b/global.json
index dab466ef7e..9596057101 100644
--- a/global.json
+++ b/global.json
@@ -13,6 +13,6 @@
}
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20411.8"
+ "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20417.6"
}
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Microsoft.Diagnostics.Monitoring.RestServer.csproj b/src/Microsoft.Diagnostics.Monitoring.RestServer/Microsoft.Diagnostics.Monitoring.RestServer.csproj
index 2d5b47fdf4..757e7331c7 100644
--- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Microsoft.Diagnostics.Monitoring.RestServer.csproj
+++ b/src/Microsoft.Diagnostics.Monitoring.RestServer/Microsoft.Diagnostics.Monitoring.RestServer.csproj
@@ -1,7 +1,7 @@
- netstandard2.0
+ netstandard2.0;netcoreapp3.1
;1591;1701
REST Api surface for dotnet-monitor
@@ -15,7 +15,7 @@
Library
-
+
diff --git a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeProviderModel.cs b/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeProviderModel.cs
index ce44f1dd6e..71a0e959ed 100644
--- a/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeProviderModel.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.RestServer/Models/EventPipeProviderModel.cs
@@ -6,7 +6,6 @@
using System.Diagnostics.Tracing;
using System.Runtime.Serialization;
using Microsoft.Diagnostics.Monitoring.RestServer.Validation;
-using Newtonsoft.Json;
namespace Microsoft.Diagnostics.Monitoring.RestServer.Models
{
diff --git a/src/Microsoft.Diagnostics.Monitoring/RuntimeInfo.cs b/src/Microsoft.Diagnostics.Monitoring/RuntimeInfo.cs
index 5c7af95c7a..e89a7aeeb9 100644
--- a/src/Microsoft.Diagnostics.Monitoring/RuntimeInfo.cs
+++ b/src/Microsoft.Diagnostics.Monitoring/RuntimeInfo.cs
@@ -1,10 +1,20 @@
-using System.IO;
+using System;
+using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Diagnostics.Monitoring
{
public static class RuntimeInfo
{
+ public static bool IsDiagnosticsEnabled
+ {
+ get
+ {
+ string enableDiagnostics = Environment.GetEnvironmentVariable("COMPlus_EnableDiagnostics");
+ return string.IsNullOrEmpty(enableDiagnostics) || !"0".Equals(enableDiagnostics, StringComparison.Ordinal);
+ }
+ }
+
public static bool IsInDockerContainer
{
get
diff --git a/src/Microsoft.Diagnostics.Monitoring/ServerEndpointInfoSource.cs b/src/Microsoft.Diagnostics.Monitoring/ServerEndpointInfoSource.cs
index 6a9d22e71a..5f79b10010 100644
--- a/src/Microsoft.Diagnostics.Monitoring/ServerEndpointInfoSource.cs
+++ b/src/Microsoft.Diagnostics.Monitoring/ServerEndpointInfoSource.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -52,10 +53,20 @@ public async ValueTask DisposeAsync()
if (null != _listenTask)
{
- await _listenTask.ConfigureAwait(false);
+ try
+ {
+ await _listenTask.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Debug.Fail(ex.Message);
+ }
}
- _server?.Dispose();
+ if (null != _server)
+ {
+ await _server.DisposeAsync().ConfigureAwait(false);
+ }
_endpointInfosSemaphore.Dispose();
@@ -68,27 +79,27 @@ public async ValueTask DisposeAsync()
///
/// Starts listening to the reversed diagnostics server for new connections.
///
- public void Listen()
+ public void Start()
{
- Listen(ReversedDiagnosticsServer.MaxAllowedConnections);
+ Start(ReversedDiagnosticsServer.MaxAllowedConnections);
}
///
/// Starts listening to the reversed diagnostics server for new connections.
///
/// The maximum number of connections the server will support.
- public void Listen(int maxConnections)
+ public void Start(int maxConnections)
{
VerifyNotDisposed();
- if (null != _server || null != _listenTask)
+ if (IsListening)
{
- throw new InvalidOperationException(nameof(ServerEndpointInfoSource.Listen) + " method can only be called once.");
+ throw new InvalidOperationException(nameof(ServerEndpointInfoSource.Start) + " method can only be called once.");
}
- _server = new ReversedDiagnosticsServer(_transportPath, maxConnections);
+ _server = new ReversedDiagnosticsServer(_transportPath);
- _listenTask = ListenAsync(_cancellation.Token);
+ _listenTask = ListenAsync(maxConnections, _cancellation.Token);
}
///
@@ -100,12 +111,15 @@ public async Task> GetEndpointInfoAsync(CancellationT
{
VerifyNotDisposed();
+ VerifyIsListening();
+
using CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellation.Token);
CancellationToken linkedToken = linkedSource.Token;
// Prune connections that no longer have an active runtime instance before
// returning the list of connections.
await _endpointInfosSemaphore.WaitAsync(linkedToken).ConfigureAwait(false);
+
try
{
// Check the transport for each endpoint info and remove it if the check fails.
@@ -146,7 +160,7 @@ private async Task PruneIfNotViable(IpcEndpointInfo info, CancellationToken toke
{
_endpointInfos.Remove(info);
OnRemovedEndpointInfo(info);
- _server.RemoveConnection(info.RuntimeInstanceCookie);
+ _server?.RemoveConnection(info.RuntimeInstanceCookie);
}
}
}
@@ -155,8 +169,9 @@ private async Task PruneIfNotViable(IpcEndpointInfo info, CancellationToken toke
/// Accepts endpoint infos from the reversed diagnostics server.
///
/// The token to monitor for cancellation requests.
- private async Task ListenAsync(CancellationToken token)
+ private async Task ListenAsync(int maxConnections, CancellationToken token)
{
+ _server.Start(maxConnections);
// Continuously accept endpoint infos from the reversed diagnostics server so
// that
// is always awaited in order to to handle new runtime instance connections
@@ -207,7 +222,7 @@ private async Task ResumeAndQueueEndpointInfo(IpcEndpointInfo info, Cancellation
}
catch (Exception)
{
- _server.RemoveConnection(info.RuntimeInstanceCookie);
+ _server?.RemoveConnection(info.RuntimeInstanceCookie);
throw;
}
@@ -229,6 +244,16 @@ private void VerifyNotDisposed()
}
}
+ private void VerifyIsListening()
+ {
+ if (!IsListening)
+ {
+ throw new InvalidOperationException(nameof(ServerEndpointInfoSource.Start) + " method must be called before invoking this operation.");
+ }
+ }
+
+ private bool IsListening => null != _server && null != _listenTask;
+
private class EndpointInfo : IEndpointInfo
{
private readonly IpcEndpointInfo _info;
diff --git a/src/Microsoft.Diagnostics.Monitoring/ServiceCollectionExtensions.cs b/src/Microsoft.Diagnostics.Monitoring/ServiceCollectionExtensions.cs
deleted file mode 100644
index 06e16cbe0c..0000000000
--- a/src/Microsoft.Diagnostics.Monitoring/ServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.Diagnostics.NETCore.Client;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Microsoft.Diagnostics.Monitoring
-{
- public static class ServiceCollectionExtensions
- {
- public static IServiceCollection AddEndpointInfoSource(this IServiceCollection services, string reversedServerAddress, int? maxConnections = null)
- {
- if (string.IsNullOrWhiteSpace(reversedServerAddress))
- {
- return services.AddSingleton();
- }
- else
- {
- // Construct the source now rather than delayed construction
- // in order to be able to accept diagnostics connections immediately.
- var serverSource = new ServerEndpointInfoSource(reversedServerAddress);
- serverSource.Listen(maxConnections.GetValueOrDefault(ReversedDiagnosticsServer.MaxAllowedConnections));
-
- return services.AddSingleton(serverSource);
- }
- }
- }
-}
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
index 89a4d4240b..06f9a0fac9 100644
--- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
@@ -174,6 +174,22 @@ internal void ResumeRuntimeFallback()
}
}
+ internal ProcessInfo GetProcessInfo()
+ {
+ IpcMessage message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo);
+ var response = IpcClient.SendMessage(_endpoint, message);
+ switch ((DiagnosticsServerResponseId)response.Header.CommandId)
+ {
+ case DiagnosticsServerResponseId.Error:
+ var hr = BitConverter.ToInt32(response.Payload, 0);
+ throw new ServerErrorException($"Get process info failed (HRESULT: 0x{hr:X8})");
+ case DiagnosticsServerResponseId.OK:
+ return ProcessInfo.Parse(response.Payload);
+ default:
+ throw new ServerErrorException($"Get process info failed - server responded with unknown command");
+ }
+ }
+
///
/// Get all the active processes that can be attached to.
///
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs
new file mode 100644
index 0000000000..91bec0c7c9
--- /dev/null
+++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ /**
+ * ==ProcessInfo==
+ * The response payload to issuing the GetProcessInfo command.
+ *
+ * 8 bytes - PID (little-endian)
+ * 16 bytes - CLR Runtime Instance Cookie (little-endian)
+ * # bytes - Command line string length and data
+ * # bytes - Operating system string length and data
+ * # bytes - Process architecture string length and data
+ *
+ * The "string length and data" fields are variable length:
+ * 4 bytes - Length of string data in UTF-16 characters
+ * (2 * length) bytes - The data of the string encoded using Unicode
+ * (includes null terminating character)
+ */
+
+ internal class ProcessInfo
+ {
+ private static readonly int GuidSizeInBytes = 16;
+
+ public static ProcessInfo Parse(byte[] payload)
+ {
+ ProcessInfo processInfo = new ProcessInfo();
+
+ int index = 0;
+ processInfo.ProcessId = BitConverter.ToUInt64(payload, index);
+ index += sizeof(UInt64);
+
+ byte[] cookieBuffer = new byte[GuidSizeInBytes];
+ Array.Copy(payload, index, cookieBuffer, 0, GuidSizeInBytes);
+ processInfo.RuntimeInstanceCookie = new Guid(cookieBuffer);
+ index += GuidSizeInBytes;
+
+ processInfo.CommandLine = ReadString(payload, ref index);
+ processInfo.OperatingSystem = ReadString(payload, ref index);
+ processInfo.ProcessArchitecture = ReadString(payload, ref index);
+
+ return processInfo;
+ }
+
+ private static string ReadString(byte[] buffer, ref int index)
+ {
+ // Length of the string of UTF-16 characters
+ int length = (int)BitConverter.ToUInt32(buffer, index);
+ index += sizeof(UInt32);
+
+ int size = (int)length * sizeof(char);
+ // The string contains an ending null character; remove it before returning the value
+ string value = Encoding.Unicode.GetString(buffer, index, size).Substring(0, length - 1);
+ index += size;
+ return value;
+ }
+
+ public UInt64 ProcessId { get; private set; }
+ public Guid RuntimeInstanceCookie { get; private set; }
+ public string CommandLine { get; private set; }
+ public string OperatingSystem { get; private set; }
+ public string ProcessArchitecture { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
index b80780a73e..b15b3b957f 100644
--- a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
+++ b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
@@ -14,6 +14,11 @@
+
+
+
+
+
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs b/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs
index 8429869797..24991a8cec 100644
--- a/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs
+++ b/src/Microsoft.Diagnostics.NETCore.Client/ReversedServer/ReversedDiagnosticsServer.cs
@@ -18,10 +18,13 @@ namespace Microsoft.Diagnostics.NETCore.Client
/// Establishes server endpoint for runtime instances to connect when
/// configured to provide diagnostic endpoints in reverse mode.
///
- internal sealed class ReversedDiagnosticsServer : IDisposable
+ internal sealed class ReversedDiagnosticsServer : IAsyncDisposable
{
// Returns true if the handler is complete and should be removed from the list
- delegate bool StreamHandler(Guid runtimeId, ref Stream stream);
+ delegate bool StreamHandler(Guid runtimeId, Stream stream, out bool consumed);
+
+ // Returns true if the handler is complete and should be removed from the list
+ delegate bool EndpointInfoHandler(IpcEndpointInfo endpointInfo, out bool consumed);
// The amount of time to allow parsing of the advertise data before cancelling. This allows the server to
// remain responsive in case the advertise data is incomplete and the stream is not closed.
@@ -30,11 +33,15 @@ internal sealed class ReversedDiagnosticsServer : IDisposable
private readonly Dictionary _cachedEndpoints = new Dictionary();
private readonly Dictionary _cachedStreams = new Dictionary();
private readonly CancellationTokenSource _disposalSource = new CancellationTokenSource();
- private readonly List _handlers = new List();
- private readonly object _lock = new object();
- private readonly IpcServerTransport _transport;
+ private readonly List _newEndpointInfoHandlers = new List();
+ private readonly List _newEndpointInfos = new List();
+ private readonly object _newEndpointInfoLock = new object();
+ private readonly List _streamHandlers = new List();
+ private readonly object _streamLock = new object();
+ private readonly string _transportPath;
private bool _disposed = false;
+ private Task _listenTask;
///
/// Constructs the instance with an endpoint bound
@@ -46,33 +53,32 @@ internal sealed class ReversedDiagnosticsServer : IDisposable
/// On all other systems, this must be the full file path of the socket.
///
public ReversedDiagnosticsServer(string transportPath)
- : this(transportPath, MaxAllowedConnections)
{
+ _transportPath = transportPath;
}
- ///
- /// Constructs the instance with an endpoint bound
- /// to the location specified by .
- ///
- ///
- /// The path of the server endpoint.
- /// On Windows, this can be a full pipe path or the name without the "\\.\pipe\" prefix.
- /// On all other systems, this must be the full file path of the socket.
- ///
- /// The maximum number of connections the server will support.
- public ReversedDiagnosticsServer(string transportPath, int maxConnections)
- {
- _transport = IpcServerTransport.Create(transportPath, maxConnections);
- }
-
- public void Dispose()
+ public async ValueTask DisposeAsync()
{
if (!_disposed)
{
_disposalSource.Cancel();
- lock (_lock)
+ if (null != _listenTask)
+ {
+ try
+ {
+ await _listenTask.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Debug.Fail(ex.Message);
+ }
+ }
+
+ lock (_streamLock)
{
+ _newEndpointInfos.Clear();
+
_cachedEndpoints.Clear();
foreach (Stream stream in _cachedStreams.Values)
@@ -82,8 +88,6 @@ public void Dispose()
_cachedStreams.Clear();
}
- _transport.Dispose();
-
_disposalSource.Dispose();
_disposed = true;
@@ -91,82 +95,56 @@ public void Dispose()
}
///
- /// Provides endpoint information when a new runtime instance connects to the server.
+ /// Starts listening at the transport path for new connections.
///
- /// The token to monitor for cancellation requests.
- /// A that contains information about the new runtime instance connection.
- ///
- /// This will only provide endpoint information on the first time a runtime connects to the server.
- /// If a connection is removed using and the same runtime instance,
- /// reconnects after this call, then a new will be produced.
- ///
- public async Task AcceptAsync(CancellationToken token)
+ public void Start()
+ {
+ Start(MaxAllowedConnections);
+ }
+
+ ///
+ /// Starts listening at the transport path for new connections.
+ ///
+ /// The maximum number of connections the server will support.
+ public void Start(int maxConnections)
{
VerifyNotDisposed();
- while (true)
+ if (IsStarted)
{
- Stream stream = null;
- IpcAdvertise advertise = null;
- try
- {
- stream = await _transport.AcceptAsync(token).ConfigureAwait(false);
- }
- catch (Exception ex) when (!(ex is OperationCanceledException))
- {
- // The advertise data could be incomplete if the runtime shuts down before completely writing
- // the information. Catch the exception and continue waiting for a new connection.
- }
+ throw new InvalidOperationException(nameof(ReversedDiagnosticsServer.Start) + " method can only be called once.");
+ }
- token.ThrowIfCancellationRequested();
+ _listenTask = ListenAsync(maxConnections, _disposalSource.Token);
+ }
- if (null != stream)
- {
- // Cancel parsing of advertise data after timeout period to
- // mitigate runtimes that write partial data and do not close the stream (avoid waiting forever).
- using var parseCancellationSource = new CancellationTokenSource();
- using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, parseCancellationSource.Token);
- try
- {
- parseCancellationSource.CancelAfter(ParseAdvertiseTimeout);
+ ///
+ /// Gets endpoint information when a new runtime instance connects to the server.
+ ///
+ /// The token to monitor for cancellation requests.
+ /// A task that completes with a value that contains information about the new runtime instance connection.
+ public async Task AcceptAsync(CancellationToken token)
+ {
+ VerifyNotDisposed();
- advertise = await IpcAdvertise.ParseAsync(stream, linkedSource.Token).ConfigureAwait(false);
- }
- catch (OperationCanceledException) when (parseCancellationSource.IsCancellationRequested)
- {
- // Only handle cancellation if it was due to the parse timeout.
- }
- catch (Exception ex) when (!(ex is OperationCanceledException))
- {
- // Catch all other exceptions and continue waiting for a new connection.
- }
- }
+ VerifyIsStarted();
- token.ThrowIfCancellationRequested();
+ var endpointInfoSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ using var methodRegistration = token.Register(() => endpointInfoSource.TrySetCanceled(token));
+ using var disposalRegistration = _disposalSource.Token.Register(
+ () => endpointInfoSource.TrySetException(new ObjectDisposedException(nameof(ReversedDiagnosticsServer))));
- if (null != advertise)
- {
- Guid runtimeCookie = advertise.RuntimeInstanceCookie;
- int pid = unchecked((int)advertise.ProcessId);
+ RegisterEndpointInfoHandler((IpcEndpointInfo endpointInfo, out bool consumed) =>
+ {
+ consumed = endpointInfoSource.TrySetResult(endpointInfo);
- lock (_lock)
- {
- ProvideStream(runtimeCookie, stream);
- // Consumers should hold onto the endpoint info and use it for diagnostic communication,
- // regardless of the number of times the same runtime instance connects. This requires consumers
- // to continuously invoke the AcceptAsync method in order to handle runtime instance reconnects,
- // even if the consumer only wants to handle a single endpoint.
- if (!_cachedEndpoints.ContainsKey(runtimeCookie))
- {
- ServerIpcEndpoint endpoint = new ServerIpcEndpoint(this, runtimeCookie);
- _cachedEndpoints.Add(runtimeCookie, endpoint);
- return new IpcEndpointInfo(endpoint, pid, runtimeCookie);
- }
- }
- }
+ // Regardless of the registrant previously waiting or cancelled,
+ // the handler should be removed from consideration.
+ return true;
+ });
- token.ThrowIfCancellationRequested();
- }
+ // Wait for the handler to verify we have a connected stream
+ return await endpointInfoSource.Task.ConfigureAwait(false);
}
///
@@ -178,10 +156,12 @@ public bool RemoveConnection(Guid runtimeCookie)
{
VerifyNotDisposed();
+ VerifyIsStarted();
+
bool endpointExisted = false;
Stream previousStream = null;
- lock (_lock)
+ lock (_streamLock)
{
endpointExisted = _cachedEndpoints.Remove(runtimeCookie);
if (endpointExisted)
@@ -206,6 +186,76 @@ private void VerifyNotDisposed()
}
}
+ private void VerifyIsStarted()
+ {
+ if (!IsStarted)
+ {
+ throw new InvalidOperationException(nameof(ReversedDiagnosticsServer.Start) + " method must be called before invoking this operation.");
+ }
+ }
+
+ ///
+ /// Listens at the transport path for new connections.
+ ///
+ /// The maximum number of connections the server will support.
+ /// The token to monitor for cancellation requests.
+ /// A task that completes when the server is no longer listening at the transport path.
+ private async Task ListenAsync(int maxConnections, CancellationToken token)
+ {
+ using var transport = IpcServerTransport.Create(_transportPath, maxConnections);
+ while (!token.IsCancellationRequested)
+ {
+ Stream stream = null;
+ IpcAdvertise advertise = null;
+ try
+ {
+ stream = await transport.AcceptAsync(token).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception)
+ {
+ // The advertise data could be incomplete if the runtime shuts down before completely writing
+ // the information. Catch the exception and continue waiting for a new connection.
+ }
+
+ if (null != stream)
+ {
+ // Cancel parsing of advertise data after timeout period to
+ // mitigate runtimes that write partial data and do not close the stream (avoid waiting forever).
+ using var parseCancellationSource = new CancellationTokenSource();
+ using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, parseCancellationSource.Token);
+ try
+ {
+ parseCancellationSource.CancelAfter(ParseAdvertiseTimeout);
+
+ advertise = await IpcAdvertise.ParseAsync(stream, linkedSource.Token).ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ if (null != advertise)
+ {
+ Guid runtimeCookie = advertise.RuntimeInstanceCookie;
+ int pid = unchecked((int)advertise.ProcessId);
+
+ lock (_streamLock)
+ {
+ ProvideStream(runtimeCookie, stream);
+ if (!_cachedEndpoints.ContainsKey(runtimeCookie))
+ {
+ ServerIpcEndpoint endpoint = new ServerIpcEndpoint(this, runtimeCookie);
+ _cachedEndpoints.Add(runtimeCookie, endpoint);
+ ProvideEndpointInfo(new IpcEndpointInfo(endpoint, pid, runtimeCookie));
+ }
+ }
+ }
+ }
+ }
+
///
/// This will block until the diagnostic stream is provided. This block can happen if
/// the stream is acquired previously and the runtime instance has not yet reconnected
@@ -215,6 +265,8 @@ internal Stream Connect(Guid runtimeId, TimeSpan timeout)
{
VerifyNotDisposed();
+ VerifyIsStarted();
+
const int StreamStatePending = 0;
const int StreamStateComplete = 1;
const int StreamStateCancelled = 2;
@@ -243,17 +295,16 @@ bool TrySetStream(int state, Stream value)
using var methodRegistration = cancellationSource.Token.Register(() => TrySetStream(StreamStateCancelled, value: null));
using var disposalRegistration = _disposalSource.Token.Register(() => TrySetStream(StreamStateDisposed, value: null));
- RegisterHandler(runtimeId, (Guid id, ref Stream cachedStream) =>
+ RegisterStreamHandler(runtimeId, (Guid id, Stream cachedStream, out bool consumed) =>
{
+ consumed = false;
+
if (id != runtimeId)
{
return false;
}
- if (TrySetStream(StreamStateComplete, cachedStream))
- {
- cachedStream = null;
- }
+ consumed = TrySetStream(StreamStateComplete, cachedStream);
// Regardless of the registrant previously waiting or cancelled,
// the handler should be removed from consideration.
@@ -280,13 +331,17 @@ internal async Task WaitForConnectionAsync(Guid runtimeId, CancellationToken tok
{
VerifyNotDisposed();
+ VerifyIsStarted();
+
var hasConnectedStreamSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
using var methodRegistration = token.Register(() => hasConnectedStreamSource.TrySetCanceled(token));
using var disposalRegistration = _disposalSource.Token.Register(
() => hasConnectedStreamSource.TrySetException(new ObjectDisposedException(nameof(ReversedDiagnosticsServer))));
- RegisterHandler(runtimeId, (Guid id, ref Stream cachedStream) =>
+ RegisterStreamHandler(runtimeId, (Guid id, Stream cachedStream, out bool consumed) =>
{
+ consumed = false;
+
if (runtimeId != id)
{
return false;
@@ -301,7 +356,7 @@ internal async Task WaitForConnectionAsync(Guid runtimeId, CancellationToken tok
if (!TestStream(cachedStream))
{
cachedStream.Dispose();
- cachedStream = null;
+ consumed = true;
return false;
}
@@ -312,14 +367,14 @@ internal async Task WaitForConnectionAsync(Guid runtimeId, CancellationToken tok
// the handler should be removed from consideration.
return true;
});
-
+
// Wait for the handler to verify we have a connected stream
await hasConnectedStreamSource.Task.ConfigureAwait(false);
}
private void ProvideStream(Guid runtimeId, Stream stream)
{
- Debug.Assert(Monitor.IsEntered(_lock));
+ Debug.Assert(Monitor.IsEntered(_streamLock));
// Get the previous stream in order to dispose it later
_cachedStreams.TryGetValue(runtimeId, out Stream previousStream);
@@ -332,24 +387,38 @@ private void ProvideStream(Guid runtimeId, Stream stream)
private void RunStreamHandlers(Guid runtimeId, Stream stream)
{
- Debug.Assert(Monitor.IsEntered(_lock));
+ Debug.Assert(Monitor.IsEntered(_streamLock));
// If there are any handlers waiting for a stream, provide
// it to the first handler in the queue.
- for (int i = 0; (i < _handlers.Count) && (null != stream); i++)
+ bool consumedStream = false;
+ for (int i = 0; !consumedStream && i < _streamHandlers.Count; i++)
{
- StreamHandler handler = _handlers[i];
- if (handler(runtimeId, ref stream))
+ StreamHandler handler = _streamHandlers[i];
+ if (handler(runtimeId, stream, out consumedStream))
{
- _handlers.RemoveAt(i);
+ _streamHandlers.RemoveAt(i);
i--;
}
}
- // Store the stream for when a handler registers later. If
- // a handler already captured the stream, this will be null, thus
- // representing that no existing stream is waiting to be consumed.
- _cachedStreams[runtimeId] = stream;
+ // Store the stream for when a handler registers later.
+ _cachedStreams[runtimeId] = consumedStream ? null : stream;
+ }
+
+ private void RegisterStreamHandler(Guid runtimeId, StreamHandler handler)
+ {
+ lock (_streamLock)
+ {
+ _cachedStreams.TryGetValue(runtimeId, out Stream stream);
+
+ _streamHandlers.Add(handler);
+
+ if (stream != null)
+ {
+ RunStreamHandlers(runtimeId, stream);
+ }
+ }
}
private bool TestStream(Stream stream)
@@ -395,24 +464,63 @@ private bool TestStream(Stream stream)
return false;
}
- private void RegisterHandler(Guid runtimeId, StreamHandler handler)
+ private void ProvideEndpointInfo(in IpcEndpointInfo endpointInfo)
{
- lock (_lock)
+ lock (_newEndpointInfoLock)
{
- if (!_cachedStreams.TryGetValue(runtimeId, out Stream stream))
+ bool consumedEndpointInfo = false;
+ // Provide the endpoint info to the first handler that will accept it.
+ for (int i = 0; !consumedEndpointInfo && i < _newEndpointInfoHandlers.Count; i++)
{
- throw new InvalidOperationException($"Runtime instance with identifier '{runtimeId}' is not registered.");
+ EndpointInfoHandler handler = _newEndpointInfoHandlers[i];
+ // Handler will return true if it has completed
+ if (handler(endpointInfo, out consumedEndpointInfo))
+ {
+ _newEndpointInfoHandlers.RemoveAt(i);
+ i--;
+ }
}
- _handlers.Add(handler);
+ // If the endpoint info was not consumed, add it to the list
+ if (!consumedEndpointInfo)
+ {
+ _newEndpointInfos.Add(endpointInfo);
+ }
+ }
+ }
- if (stream != null)
+ private void RegisterEndpointInfoHandler(EndpointInfoHandler handler)
+ {
+ lock (_newEndpointInfoLock)
+ {
+ // Attempt to accept an endpoint info
+ for (int i = 0; i < _newEndpointInfos.Count && null != handler; i++)
{
- RunStreamHandlers(runtimeId, stream);
+ bool consumedEnpointInfo = false;
+ // Handler will return true if it has completed
+ if (handler(_newEndpointInfos[i], out consumedEnpointInfo))
+ {
+ handler = null;
+ }
+
+ // If the endpoint info was consumed, remove it from the list
+ if (consumedEnpointInfo)
+ {
+ _newEndpointInfos.RemoveAt(i);
+ i--;
+ }
+ }
+
+ // If the handler did not signal completion, then add it to the handlers list.
+ if (null != handler)
+ {
+ _newEndpointInfoHandlers.Add(handler);
}
}
}
+ private bool IsStarted => null != _listenTask;
+
public static int MaxAllowedConnections = IpcServerTransport.MaxAllowedConnections;
}
}
diff --git a/src/Tools/dotnet-monitor/DiagnosticPortConfiguration.cs b/src/Tools/dotnet-monitor/DiagnosticPortConfiguration.cs
new file mode 100644
index 0000000000..4ed543411c
--- /dev/null
+++ b/src/Tools/dotnet-monitor/DiagnosticPortConfiguration.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ public class DiagnosticPortConfiguration
+ {
+ public DiagnosticPortConnectionMode ConnectionMode { get; set; }
+
+ public string EndpointName { get; set; }
+
+ public int? MaxConnections { get; set; }
+ }
+
+ public enum DiagnosticPortConnectionMode
+ {
+ Connect,
+ Listen
+ }
+}
diff --git a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs b/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs
index d2a90e896b..6aa568d90a 100644
--- a/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs
+++ b/src/Tools/dotnet-monitor/DiagnosticsMonitorCommandHandler.cs
@@ -40,6 +40,7 @@ public IWebHostBuilder CreateWebHostBuilder(IConsole console, string[] urls, str
IWebHostBuilder builder = WebHost.CreateDefaultBuilder()
.ConfigureAppConfiguration((IConfigurationBuilder builder) =>
{
+ ConfigureEndpointInfoSource(builder, reversedServerAddress);
if (metrics)
{
//Note these are in precedence order.
@@ -50,8 +51,10 @@ public IWebHostBuilder CreateWebHostBuilder(IConsole console, string[] urls, str
})
.ConfigureServices((WebHostBuilderContext context, IServiceCollection services) =>
{
- services.AddEndpointInfoSource(reversedServerAddress);
//TODO Many of these service additions should be done through extension methods
+ services.Configure(context.Configuration.GetSection(nameof(DiagnosticPortConfiguration)));
+ services.AddSingleton();
+ services.AddHostedService();
services.AddSingleton();
if (metrics)
{
@@ -75,6 +78,16 @@ private static void ConfigureMetricsEndpoint(IConfigurationBuilder builder, stri
});
}
+ private static void ConfigureEndpointInfoSource(IConfigurationBuilder builder, string diagnosticPort)
+ {
+ DiagnosticPortConnectionMode connectionMode = string.IsNullOrEmpty(diagnosticPort) ? DiagnosticPortConnectionMode.Connect : DiagnosticPortConnectionMode.Listen;
+ builder.AddInMemoryCollection(new Dictionary
+ {
+ {MakeKey(nameof(DiagnosticPortConfiguration), nameof(DiagnosticPortConfiguration.ConnectionMode)), connectionMode.ToString()},
+ {MakeKey(nameof(DiagnosticPortConfiguration), nameof(DiagnosticPortConfiguration.EndpointName)), diagnosticPort}
+ });
+ }
+
private static string MakeKey(string parent, string child)
{
return FormattableString.Invariant($"{parent}:{child}");
diff --git a/src/Tools/dotnet-monitor/FilteredEndpointInfoSource.cs b/src/Tools/dotnet-monitor/FilteredEndpointInfoSource.cs
new file mode 100644
index 0000000000..a22754af56
--- /dev/null
+++ b/src/Tools/dotnet-monitor/FilteredEndpointInfoSource.cs
@@ -0,0 +1,110 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Diagnostics.Monitoring;
+using Microsoft.Diagnostics.NETCore.Client;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ ///
+ /// Wraps an based on the provided configuration
+ /// and filters the current process from consideration.
+ ///
+ internal class FilteredEndpointInfoSource : IEndpointInfoSourceInternal, IAsyncDisposable
+ {
+ private readonly DiagnosticPortConfiguration _configuration;
+ private readonly int? _processIdToFilterOut;
+ private readonly Guid? _runtimeInstanceCookieToFilterOut;
+ private readonly IEndpointInfoSourceInternal _source;
+
+ public FilteredEndpointInfoSource(IOptions configuration)
+ {
+ _configuration = configuration.Value;
+ switch (_configuration.ConnectionMode)
+ {
+ case DiagnosticPortConnectionMode.Connect:
+ _source = new ClientEndpointInfoSource();
+ break;
+ case DiagnosticPortConnectionMode.Listen:
+ _source = new ServerEndpointInfoSource(_configuration.EndpointName);
+ break;
+ default:
+ throw new InvalidOperationException($"Unhandled connection mode: {_configuration.ConnectionMode}");
+ }
+
+ // Filter out the current process based on the connection mode.
+ if (RuntimeInfo.IsDiagnosticsEnabled)
+ {
+ int pid = Process.GetCurrentProcess().Id;
+
+ // Regardless of connection mode, can use the runtime instance cookie to filter self out.
+ try
+ {
+ var client = new DiagnosticsClient(pid);
+ Guid runtimeInstanceCookie = client.GetProcessInfo().RuntimeInstanceCookie;
+ if (Guid.Empty != runtimeInstanceCookie)
+ {
+ _runtimeInstanceCookieToFilterOut = runtimeInstanceCookie;
+ }
+ }
+ catch (Exception)
+ {
+ }
+
+ // If connecting to runtime instances, filter self out. In listening mode, it's likely
+ // that multiple processes have the same PID in multi-container scenarios.
+ if (DiagnosticPortConnectionMode.Connect == configuration.Value.ConnectionMode)
+ {
+ _processIdToFilterOut = pid;
+ }
+ }
+ }
+
+ public async Task> GetEndpointInfoAsync(CancellationToken token)
+ {
+ var endpointInfos = await _source.GetEndpointInfoAsync(token);
+
+ // Apply process ID filter
+ if (_processIdToFilterOut.HasValue)
+ {
+ endpointInfos = endpointInfos.Where(info => info.ProcessId != _processIdToFilterOut.Value);
+ }
+
+ // Apply runtime instance cookie filter
+ if (_runtimeInstanceCookieToFilterOut.HasValue)
+ {
+ endpointInfos = endpointInfos.Where(info => info.RuntimeInstanceCookie != _runtimeInstanceCookieToFilterOut.Value);
+ }
+
+ return endpointInfos;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (_source is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
+ else if (_source is IAsyncDisposable asyncDisposable)
+ {
+ await asyncDisposable.ConfigureAwait(false).DisposeAsync();
+ }
+ }
+
+ public void Start()
+ {
+ if (_source is ServerEndpointInfoSource source)
+ {
+ source.Start(_configuration.MaxConnections.GetValueOrDefault(ReversedDiagnosticsServer.MaxAllowedConnections));
+ }
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/FilteredEndpointInfoSourceHostedService.cs b/src/Tools/dotnet-monitor/FilteredEndpointInfoSourceHostedService.cs
new file mode 100644
index 0000000000..d72de2b6fa
--- /dev/null
+++ b/src/Tools/dotnet-monitor/FilteredEndpointInfoSourceHostedService.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Diagnostics.Monitoring;
+using Microsoft.Extensions.Hosting;
+
+namespace Microsoft.Diagnostics.Tools.Monitor
+{
+ ///
+ /// A hosted service that ensures the
+ /// starts monitoring for connectable processes.
+ ///
+ internal class FilteredEndpointInfoSourceHostedService : IHostedService
+ {
+ private readonly FilteredEndpointInfoSource _source;
+
+ public FilteredEndpointInfoSourceHostedService(IEndpointInfoSource source)
+ {
+ _source = (FilteredEndpointInfoSource)source;
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _source.Start();
+
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Tools/dotnet-monitor/Startup.cs b/src/Tools/dotnet-monitor/Startup.cs
index 7d91045508..50222bf9db 100644
--- a/src/Tools/dotnet-monitor/Startup.cs
+++ b/src/Tools/dotnet-monitor/Startup.cs
@@ -2,19 +2,18 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
using System.Collections.Generic;
using System.IO.Compression;
-using System.Linq;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCompression;
-using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Diagnostics.Monitoring.RestServer;
+using Microsoft.Diagnostics.Monitoring.RestServer.Controllers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
namespace Microsoft.Diagnostics.Monitoring
{
@@ -30,14 +29,14 @@ public Startup(IConfiguration configuration)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
- services.AddMvc((MvcOptions options) =>
- {
- options.Filters.Add(new ProducesAttribute("application/json"));
+ services.AddMvc(options =>
+ {
+ options.Filters.Add(new ProducesAttribute("application/json"));
- // HACK We need to disable EndpointRouting in order to run properly in 3.1
- System.Reflection.PropertyInfo prop = options.GetType().GetProperty("EnableEndpointRouting");
- prop?.SetValue(options, false);
- }).SetCompatibilityVersion(CompatibilityVersion.Latest);
+ options.EnableEndpointRouting = false;
+ })
+ .SetCompatibilityVersion(CompatibilityVersion.Latest)
+ .AddApplicationPart(typeof(DiagController).Assembly);
services.Configure(options =>
{
@@ -50,20 +49,27 @@ public void ConfigureServices(IServiceCollection services)
};
});
- services.Configure(options =>
+ services.Configure(options =>
{
options.Level = CompressionLevel.Optimal;
});
services.AddResponseCompression(configureOptions =>
{
- configureOptions.Providers.Add();
+ configureOptions.Providers.Add();
configureOptions.MimeTypes = new List { "application/octet-stream" };
});
- var config = new PrometheusConfiguration();
- Configuration.Bind(nameof(PrometheusConfiguration), config);
- if (config.Enabled)
+ // This is needed to allow the StreamingLogger to synchronously write to the output stream.
+ // Eventually should switch StreamingLoggger to something that allows for async operations.
+ services.Configure(options =>
+ {
+ options.AllowSynchronousIO = true;
+ });
+
+ var prometheusConfig = new PrometheusConfiguration();
+ Configuration.Bind(nameof(PrometheusConfiguration), prometheusConfig);
+ if (prometheusConfig.Enabled)
{
services.AddSingleton();
services.AddHostedService();
@@ -71,7 +77,7 @@ public void ConfigureServices(IServiceCollection services)
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
diff --git a/src/Tools/dotnet-monitor/dotnet-monitor.csproj b/src/Tools/dotnet-monitor/dotnet-monitor.csproj
index ddeee4220f..e76cc6674e 100644
--- a/src/Tools/dotnet-monitor/dotnet-monitor.csproj
+++ b/src/Tools/dotnet-monitor/dotnet-monitor.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.1
+ netcoreapp3.1
linux-x64;linux-musl-x64;win-x64
linux-x64;linux-musl-x64;win-x64
Microsoft.Diagnostics.Tools.Monitor
@@ -10,6 +10,7 @@
Diagnostic
false
$(Description)
+ Major
monitor
diff --git a/src/Tools/dotnet-monitor/runtimeconfig.template.json b/src/Tools/dotnet-monitor/runtimeconfig.template.json
deleted file mode 100644
index f022b7ffce..0000000000
--- a/src/Tools/dotnet-monitor/runtimeconfig.template.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "rollForwardOnNoCandidateFx": 2
-}
\ No newline at end of file
diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs
index 27ab0f6b1a..27d0e9b010 100644
--- a/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs
+++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/ReversedServerTests.cs
@@ -22,23 +22,50 @@ public ReversedServerTests(ITestOutputHelper outputHelper)
_outputHelper = outputHelper;
}
+ ///
+ /// Tests that server throws appropriate exceptions when not started.
+ ///
+ [Fact]
+ public async Task ReversedServerNoStartTest()
+ {
+ await using var server = CreateReversedServer(out string transportName);
+ // Intentionally did not start server
+
+ TimeSpan CancellationTimeout = TimeSpan.FromSeconds(1);
+ using CancellationTokenSource cancellation = new CancellationTokenSource(CancellationTimeout);
+
+ // All API surface (except for Start) should throw InvalidOperationException
+ await Assert.ThrowsAsync(
+ () => server.AcceptAsync(cancellation.Token));
+
+ Assert.Throws(
+ () => server.Connect(Guid.Empty, CancellationTimeout));
+
+ Assert.Throws(
+ () => server.RemoveConnection(Guid.Empty));
+
+ await Assert.ThrowsAsync(
+ () => server.WaitForConnectionAsync(Guid.Empty, cancellation.Token));
+ }
+
///
/// Tests that server throws appropriate exceptions when disposed.
///
[Fact]
public async Task ReversedServerDisposeTest()
{
- var server = StartReversedServer(out string transportName);
+ var server = CreateReversedServer(out string transportName);
+ server.Start();
using CancellationTokenSource cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(1));
Task acceptTask = server.AcceptAsync(cancellation.Token);
// Validate server surface throws after disposal
- server.Dispose();
+ await server.DisposeAsync();
- // Pending tasks should be cancelled and throw TaskCanceledException
- await Assert.ThrowsAnyAsync(() => acceptTask);
- Assert.True(acceptTask.IsCanceled);
+ // Pending tasks should throw ObjectDisposedException
+ await Assert.ThrowsAnyAsync(() => acceptTask);
+ Assert.True(acceptTask.IsFaulted);
// Calls after dispose should throw ObjectDisposedException
await Assert.ThrowsAsync(
@@ -55,7 +82,8 @@ await Assert.ThrowsAsync(
[Fact]
public async Task ReversedServerAcceptAsyncYieldsTest()
{
- using var server = StartReversedServer(out string transportName);
+ await using var server = CreateReversedServer(out string transportName);
+ server.Start();
using var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
@@ -72,18 +100,19 @@ public async Task ReversedServerAcceptAsyncYieldsTest()
[Fact]
public async Task ReversedServerNonExistingRuntimeIdentifierTest()
{
- using var server = StartReversedServer(out string transportName);
+ await using var server = CreateReversedServer(out string transportName);
+ server.Start();
Guid nonExistingRuntimeId = Guid.NewGuid();
using CancellationTokenSource cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(1));
_outputHelper.WriteLine($"Testing {nameof(ReversedDiagnosticsServer.WaitForConnectionAsync)}");
- await Assert.ThrowsAsync(
+ await Assert.ThrowsAsync(
() => server.WaitForConnectionAsync(nonExistingRuntimeId, cancellation.Token));
_outputHelper.WriteLine($"Testing {nameof(ReversedDiagnosticsServer.Connect)}");
- Assert.Throws(
+ Assert.Throws(
() => server.Connect(nonExistingRuntimeId, TimeSpan.FromSeconds(1)));
_outputHelper.WriteLine($"Testing {nameof(ReversedDiagnosticsServer.RemoveConnection)}");
@@ -102,8 +131,8 @@ await Assert.ThrowsAsync(
[Fact]
public async Task ReversedServerSingleTargetMultipleUseClientTest()
{
- using var server = StartReversedServer(out string transportName);
- await using var accepter = new EndpointInfoAccepter(server, _outputHelper);
+ await using var server = CreateReversedServer(out string transportName);
+ server.Start();
TestRunner runner = null;
IpcEndpointInfo info;
@@ -112,12 +141,12 @@ public async Task ReversedServerSingleTargetMultipleUseClientTest()
// Start client pointing to diagnostics server
runner = StartTracee(transportName);
- info = await AcceptAsync(accepter);
+ info = await AcceptAsync(server);
await VerifyEndpointInfo(runner, info);
// There should not be any new endpoint infos
- await VerifyNoNewEndpointInfos(accepter);
+ await VerifyNoNewEndpointInfos(server);
ResumeRuntime(info);
@@ -138,7 +167,7 @@ public async Task ReversedServerSingleTargetMultipleUseClientTest()
Assert.True(server.RemoveConnection(info.RuntimeInstanceCookie), "Expected to be able to remove connection from server.");
// There should not be any more endpoint infos
- await VerifyNoNewEndpointInfos(accepter);
+ await VerifyNoNewEndpointInfos(server);
}
///
@@ -147,8 +176,8 @@ public async Task ReversedServerSingleTargetMultipleUseClientTest()
[Fact]
public async Task ReversedServerSingleTargetExitsClientInviableTest()
{
- using var server = StartReversedServer(out string transportName);
- await using var accepter = new EndpointInfoAccepter(server, _outputHelper);
+ await using var server = CreateReversedServer(out string transportName);
+ server.Start();
TestRunner runner = null;
IpcEndpointInfo info;
@@ -158,12 +187,12 @@ public async Task ReversedServerSingleTargetExitsClientInviableTest()
runner = StartTracee(transportName);
// Get client connection
- info = await AcceptAsync(accepter);
+ info = await AcceptAsync(server);
await VerifyEndpointInfo(runner, info);
// There should not be any new endpoint infos
- await VerifyNoNewEndpointInfos(accepter);
+ await VerifyNoNewEndpointInfos(server);
ResumeRuntime(info);
@@ -184,21 +213,21 @@ public async Task ReversedServerSingleTargetExitsClientInviableTest()
Assert.True(server.RemoveConnection(info.RuntimeInstanceCookie), "Expected to be able to remove connection from server.");
// There should not be any more endpoint infos
- await VerifyNoNewEndpointInfos(accepter);
+ await VerifyNoNewEndpointInfos(server);
}
- private ReversedDiagnosticsServer StartReversedServer(out string transportName)
+ private ReversedDiagnosticsServer CreateReversedServer(out string transportName)
{
transportName = ReversedServerHelper.CreateServerTransportName();
_outputHelper.WriteLine("Starting reversed server at '" + transportName + "'.");
return new ReversedDiagnosticsServer(transportName);
}
- private async Task AcceptAsync(EndpointInfoAccepter accepter)
+ private async Task AcceptAsync(ReversedDiagnosticsServer server)
{
using (var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
- return await accepter.AcceptAsync(cancellationSource.Token);
+ return await server.AcceptAsync(cancellationSource.Token);
}
}
@@ -230,14 +259,14 @@ await Assert.ThrowsAsync(
///
/// Checks that the accepter does not provide a new endpoint info.
///
- private async Task VerifyNoNewEndpointInfos(EndpointInfoAccepter accepter)
+ private async Task VerifyNoNewEndpointInfos(ReversedDiagnosticsServer server)
{
_outputHelper.WriteLine("Verifying there are no more connections.");
using var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
- Task acceptTask = accepter.AcceptAsync(cancellationSource.Token);
- await Assert.ThrowsAsync(() => acceptTask);
+ Task acceptTask = server.AcceptAsync(cancellationSource.Token);
+ await Assert.ThrowsAsync(() => acceptTask);
Assert.True(acceptTask.IsCanceled);
_outputHelper.WriteLine("Verified there are no more connections.");
@@ -361,88 +390,5 @@ private Task VerifyEventStreamProvidesEventsAsync(IpcEndpointInfo info, EventPip
await stoppedProcessingTask;
});
}
-
- ///
- /// Helper class for consuming endpoint infos from the reverse diagnostics server.
- ///
- ///
- /// The diagnostics server requires that something is continuously attempting to accept endpoint infos
- /// in order to process incoming connections. This helps facilitate that continuous accepting of
- /// endpoint infos so the individual tests don't have to know about the behavior.
- ///
- private class EndpointInfoAccepter : IAsyncDisposable
- {
- private readonly CancellationTokenSource _cancellation = new CancellationTokenSource();
- private readonly Queue _connections = new Queue();
- private readonly SemaphoreSlim _connectionsSemaphore = new SemaphoreSlim(0);
- private readonly Task _listenTask;
- private readonly ITestOutputHelper _outputHelper;
- private readonly ReversedDiagnosticsServer _server;
-
- private int _acceptedCount;
- private bool _disposed;
-
- public EndpointInfoAccepter(ReversedDiagnosticsServer server, ITestOutputHelper outputHelper)
- {
- _server = server;
- _outputHelper = outputHelper;
-
- _listenTask = ListenAsync(_cancellation.Token);
- }
-
- public async ValueTask DisposeAsync()
- {
- if (!_disposed)
- {
- _cancellation.Cancel();
-
- await _listenTask;
-
- _cancellation.Dispose();
-
- _disposed = true;
- }
- }
-
- public async Task AcceptAsync(CancellationToken token)
- {
- using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellation.Token);
-
- _outputHelper.WriteLine("Waiting for connection from accepter.");
- await _connectionsSemaphore.WaitAsync(linkedSource.Token).ConfigureAwait(false);
- _outputHelper.WriteLine("Received connection from accepter.");
-
- return _connections.Dequeue();
- }
-
- ///
- /// Continuously accept endpoint infos from the reversed diagnostics server so
- /// that
- /// is always awaited in order to to handle new runtime instance connections
- /// as well as existing runtime instance reconnections.
- ///
- private async Task ListenAsync(CancellationToken token)
- {
- while (!token.IsCancellationRequested)
- {
- IpcEndpointInfo info;
- try
- {
- _outputHelper.WriteLine("Waiting for connection from server.");
- info = await _server.AcceptAsync(token).ConfigureAwait(false);
-
- _acceptedCount++;
- _outputHelper.WriteLine($"Accepted connection #{_acceptedCount} from server: {info.ToTestString()}");
- }
- catch (OperationCanceledException)
- {
- break;
- }
-
- _connections.Enqueue(info);
- _connectionsSemaphore.Release();
- }
- }
- }
}
}
diff --git a/src/tests/dotnet-monitor/EndpointInfoSourceTests.cs b/src/tests/dotnet-monitor/EndpointInfoSourceTests.cs
index 9d973aab3b..9b31e02112 100644
--- a/src/tests/dotnet-monitor/EndpointInfoSourceTests.cs
+++ b/src/tests/dotnet-monitor/EndpointInfoSourceTests.cs
@@ -23,27 +23,20 @@ public EndpointInfoSourceTests(ITestOutputHelper outputHelper)
}
///
- /// Tests that the server endpoint info source has no connections
- /// if is not called.
+ /// Tests that other methods throw if
+ /// is not called.
///
[Fact]
- public async Task ServerSourceNoListenTest()
+ public async Task ServerSourceNoStartTest()
{
await using var source = CreateServerSource(out string transportName);
- // Intentionally do not call Listen
+ // Intentionally do not call Start
- await using (var execution1 = StartTraceeProcess("LoggerRemoteTest", transportName))
- {
- execution1.Start();
-
- await Task.Delay(TimeSpan.FromSeconds(1));
+ TimeSpan CancellationTimeout = TimeSpan.FromSeconds(1);
+ using CancellationTokenSource cancellation = new CancellationTokenSource(CancellationTimeout);
- var endpointInfos = await GetEndpointInfoAsync(source);
-
- Assert.Empty(endpointInfos);
-
- _outputHelper.WriteLine("Stopping tracee.");
- }
+ await Assert.ThrowsAsync(
+ () => source.GetEndpointInfoAsync(cancellation.Token));
}
///
@@ -53,7 +46,7 @@ public async Task ServerSourceNoListenTest()
public async Task ServerSourceNoConnectionsTest()
{
await using var source = CreateServerSource(out _);
- source.Listen();
+ source.Start();
var endpointInfos = await GetEndpointInfoAsync(source);
Assert.Empty(endpointInfos);
@@ -67,16 +60,16 @@ public async Task ServerSourceNoConnectionsTest()
public async Task ServerSourceThrowsWhenDisposedTest()
{
var source = CreateServerSource(out _);
- source.Listen();
+ source.Start();
await source.DisposeAsync();
// Validate source surface throws after disposal
Assert.Throws(
- () => source.Listen());
+ () => source.Start());
Assert.Throws(
- () => source.Listen(1));
+ () => source.Start(1));
await Assert.ThrowsAsync(
() => source.GetEndpointInfoAsync(CancellationToken.None));
@@ -84,20 +77,20 @@ await Assert.ThrowsAsync(
///
/// Tests that server endpoint info source should throw an exception from
- /// and
- /// after listening was already started.
+ /// and
+ /// after listening was already started.
///
[Fact]
- public async Task ServerSourceThrowsWhenMultipleListenTest()
+ public async Task ServerSourceThrowsWhenMultipleStartTest()
{
await using var source = CreateServerSource(out _);
- source.Listen();
+ source.Start();
Assert.Throws(
- () => source.Listen());
+ () => source.Start());
Assert.Throws(
- () => source.Listen(1));
+ () => source.Start(1));
}
///
@@ -108,7 +101,7 @@ public async Task ServerSourceThrowsWhenMultipleListenTest()
public async Task ServerSourceAddRemoveSingleConnectionTest()
{
await using var source = CreateServerSource(out string transportName);
- source.Listen();
+ source.Start();
var endpointInfos = await GetEndpointInfoAsync(source);
Assert.Empty(endpointInfos);