Skip to content

[main] Source code updates from dotnet/diagnostics #1504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/diagnostics/eng/InstallRuntimes.proj
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
</Target>

<!--
Writes the Debugger.Tests.Versions.txt file used by the SOS test harness
Writes the Debugger.Tests.Versions.txt file used by tests in this repo to determine which runtimes to test against
-->

<Target Name="WriteTestVersionManifest"
Expand Down
2 changes: 1 addition & 1 deletion src/diagnostics/eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<MicrosoftBclAsyncInterfacesVersion>6.0.0</MicrosoftBclAsyncInterfacesVersion>
<MicrosoftDiagnosticsRuntimeVersion>4.0.0-beta.25359.1</MicrosoftDiagnosticsRuntimeVersion>
<MicrosoftDiaSymReaderNativeVersion>17.10.0-beta1.24272.1</MicrosoftDiaSymReaderNativeVersion>
<MicrosoftDiagnosticsTracingTraceEventVersion>3.1.21</MicrosoftDiagnosticsTracingTraceEventVersion>
<MicrosoftDiagnosticsTracingTraceEventVersion>3.1.23</MicrosoftDiagnosticsTracingTraceEventVersion>
<MicrosoftExtensionsLoggingVersion>6.0.0</MicrosoftExtensionsLoggingVersion>
<MicrosoftExtensionsLoggingAbstractionsVersion>6.0.4</MicrosoftExtensionsLoggingAbstractionsVersion>
<MicrosoftExtensionsLoggingConsoleVersion>6.0.0</MicrosoftExtensionsLoggingConsoleVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public UnsupportedProtocolException(string msg) : base(msg) { }
public class ServerNotAvailableException : DiagnosticsClientException
{
public ServerNotAvailableException(string msg) : base(msg) { }
public ServerNotAvailableException(string msg, Exception exception) : base(msg, exception) { }
}

// When the runtime responded with an error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
Expand All @@ -20,6 +21,7 @@ internal abstract class IpcEndpoint
/// </summary>
/// <param name="timeout">The amount of time to block attempting to connect</param>
/// <returns>A stream used for writing and reading data to and from the target .NET process</returns>
/// <throws>ServerNotAvailableException</throws>
public abstract Stream Connect(TimeSpan timeout);

/// <summary>
Expand All @@ -29,6 +31,7 @@ internal abstract class IpcEndpoint
/// <returns>
/// A task that completes with a stream used for writing and reading data to and from the target .NET process.
/// </returns>
/// <throws>ServerNotAvailableException</throws>
public abstract Task<Stream> ConnectAsync(CancellationToken token);

/// <summary>
Expand All @@ -51,66 +54,81 @@ internal static class IpcEndpointHelper
{
public static Stream Connect(IpcEndpointConfig config, TimeSpan timeout)
{
if (config.Transport == IpcEndpointConfig.TransportType.NamedPipe)
{
NamedPipeClientStream namedPipe = new(
".",
config.Address,
PipeDirection.InOut,
PipeOptions.None,
TokenImpersonationLevel.Impersonation);
namedPipe.Connect((int)timeout.TotalMilliseconds);
return namedPipe;
}
else if (config.Transport == IpcEndpointConfig.TransportType.UnixDomainSocket)
try
{
IpcUnixDomainSocket socket = new();
socket.Connect(new IpcUnixDomainSocketEndPoint(config.Address), timeout);
return new ExposedSocketNetworkStream(socket, ownsSocket: true);
}
if (config.Transport == IpcEndpointConfig.TransportType.NamedPipe)
{
NamedPipeClientStream namedPipe = new(
".",
config.Address,
PipeDirection.InOut,
PipeOptions.None,
TokenImpersonationLevel.Impersonation);
namedPipe.Connect((int)timeout.TotalMilliseconds);
return namedPipe;
}
else if (config.Transport == IpcEndpointConfig.TransportType.UnixDomainSocket)
{
IpcUnixDomainSocket socket = new();
socket.Connect(new IpcUnixDomainSocketEndPoint(config.Address), timeout);
return new ExposedSocketNetworkStream(socket, ownsSocket: true);
}
#if DIAGNOSTICS_RUNTIME
else if (config.Transport == IpcEndpointConfig.TransportType.TcpSocket)
{
var tcpClient = new TcpClient ();
var endPoint = new IpcTcpSocketEndPoint(config.Address);
tcpClient.Connect(endPoint.EndPoint);
return tcpClient.GetStream();
}
else if (config.Transport == IpcEndpointConfig.TransportType.TcpSocket)
{
var tcpClient = new TcpClient ();
var endPoint = new IpcTcpSocketEndPoint(config.Address);
tcpClient.Connect(endPoint.EndPoint);
return tcpClient.GetStream();
}
#endif
else
else
{
throw new ArgumentException($"Unsupported IpcEndpointConfig transport type {config.Transport}");
}

}
catch (SocketException ex)
{
throw new ArgumentException($"Unsupported IpcEndpointConfig transport type {config.Transport}");
throw new ServerNotAvailableException($"Unable to connect to the server. {ex.Message}", ex);
}
}

public static async Task<Stream> ConnectAsync(IpcEndpointConfig config, CancellationToken token)
{
if (config.Transport == IpcEndpointConfig.TransportType.NamedPipe)
{
NamedPipeClientStream namedPipe = new(
".",
config.Address,
PipeDirection.InOut,
PipeOptions.Asynchronous,
TokenImpersonationLevel.Impersonation);

// Pass non-infinite timeout in order to cause internal connection algorithm
// to check the CancellationToken periodically. Otherwise, if the named pipe
// is waited using WaitNamedPipe with an infinite timeout, then the
// CancellationToken cannot be observed.
await namedPipe.ConnectAsync(int.MaxValue, token).ConfigureAwait(false);

return namedPipe;
}
else if (config.Transport == IpcEndpointConfig.TransportType.UnixDomainSocket)
try
{
IpcUnixDomainSocket socket = new();
await socket.ConnectAsync(new IpcUnixDomainSocketEndPoint(config.Address), token).ConfigureAwait(false);
return new ExposedSocketNetworkStream(socket, ownsSocket: true);
if (config.Transport == IpcEndpointConfig.TransportType.NamedPipe)
{
NamedPipeClientStream namedPipe = new(
".",
config.Address,
PipeDirection.InOut,
PipeOptions.Asynchronous,
TokenImpersonationLevel.Impersonation);

// Pass non-infinite timeout in order to cause internal connection algorithm
// to check the CancellationToken periodically. Otherwise, if the named pipe
// is waited using WaitNamedPipe with an infinite timeout, then the
// CancellationToken cannot be observed.
await namedPipe.ConnectAsync(int.MaxValue, token).ConfigureAwait(false);

return namedPipe;
}
else if (config.Transport == IpcEndpointConfig.TransportType.UnixDomainSocket)
{
IpcUnixDomainSocket socket = new();
await socket.ConnectAsync(new IpcUnixDomainSocketEndPoint(config.Address), token).ConfigureAwait(false);
return new ExposedSocketNetworkStream(socket, ownsSocket: true);
}
else
{
throw new ArgumentException($"Unsupported IpcEndpointConfig transport type {config.Transport}");
}
}
else
catch (SocketException ex)
{
throw new ArgumentException($"Unsupported IpcEndpointConfig transport type {config.Transport}");
throw new ServerNotAvailableException($"Unable to connect to the server. {ex.Message}", ex);
}
}
}
Expand Down Expand Up @@ -221,10 +239,13 @@ internal class PidIpcEndpoint : IpcEndpoint
{
public static string IpcRootPath { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"\\.\pipe\" : Path.GetTempPath();
public static string DiagnosticsPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnet-diagnostic-(\d+)$" : @"^dotnet-diagnostic-(\d+)-(\d+)-socket$";

// Format strings as private const members
private const string _defaultAddressFormatWindows = "dotnet-diagnostic-{0}";
private const string _dsrouterAddressFormatWindows = "dotnet-diagnostic-dsrouter-{0}";
private const string _defaultAddressFormatNonWindows = "dotnet-diagnostic-{0}-{1}-socket";
private const string _dsrouterAddressFormatNonWindows = "dotnet-diagnostic-dsrouter-{0}-{1}-socket";
private int _pid;
private IpcEndpointConfig _config;

/// <summary>
/// Creates a reference to a .NET process's IPC Transport
/// using the default rules for a given pid
Expand Down Expand Up @@ -271,11 +292,11 @@ private static bool TryGetDefaultAddress(int pid, out string defaultAddress)

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
defaultAddress = $"dotnet-diagnostic-{pid}";
defaultAddress = string.Format(_defaultAddressFormatWindows, pid);

try
{
string dsrouterAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-dsrouter-{pid}").FirstOrDefault();
string dsrouterAddress = Directory.GetFiles(IpcRootPath, string.Format(_dsrouterAddressFormatWindows, pid)).FirstOrDefault();
if (!string.IsNullOrEmpty(dsrouterAddress))
{
defaultAddress = dsrouterAddress;
Expand All @@ -287,11 +308,11 @@ private static bool TryGetDefaultAddress(int pid, out string defaultAddress)
{
try
{
defaultAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{pid}-*-socket") // Try best match.
defaultAddress = Directory.GetFiles(IpcRootPath, string.Format(_defaultAddressFormatNonWindows, pid, "*")) // Try best match.
.OrderByDescending(f => new FileInfo(f).LastWriteTime)
.FirstOrDefault();

string dsrouterAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-dsrouter-{pid}-*-socket") // Try best match.
string dsrouterAddress = Directory.GetFiles(IpcRootPath, string.Format(_dsrouterAddressFormatNonWindows, pid, "*")) // Try best match.
.OrderByDescending(f => new FileInfo(f).LastWriteTime)
.FirstOrDefault();

Expand Down Expand Up @@ -332,8 +353,15 @@ public static string GetDefaultAddress(int pid)
string msg = $"Unable to connect to Process {pid}.";
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
int total_length = IpcRootPath.Length + string.Format(_defaultAddressFormatNonWindows, pid, "##########").Length;
if (total_length > 108) // This isn't perfect as we don't know the disambiguation key length. However it should catch most cases.
{
msg += "The total length of the diagnostic socket path may exceed 108 characters. " +
"Try setting the TMPDIR environment variable to a shorter path";
}
msg += $" Please verify that {IpcRootPath} is writable by the current user. "
+ "If the target process has environment variable TMPDIR set, please set TMPDIR to the same directory. "
+ "Please also ensure that the target process has {TMPDIR}/dotnet-diagnostic-{pid}-{disambiguation_key}-socket shorter than 108 characters. "
+ "Please see https://aka.ms/dotnet-diagnostics-port for more information";
}
throw new ServerNotAvailableException(msg);
Expand All @@ -349,7 +377,7 @@ public static bool IsDefaultAddressDSRouter(int pid, string address)
address = address.Substring(IpcRootPath.Length);
}

string dsrouterAddress = $"dotnet-diagnostic-dsrouter-{pid}";
string dsrouterAddress = string.Format(_dsrouterAddressFormatWindows, pid);
return address.StartsWith(dsrouterAddress, StringComparison.OrdinalIgnoreCase);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ public class SdkPrebuiltDebuggeeCompiler : IDebuggeeCompiler

public SdkPrebuiltDebuggeeCompiler(TestConfiguration config, string debuggeeName)
{
if (string.IsNullOrEmpty(config.TargetConfiguration))
{
throw new System.ArgumentException("TargetConfiguration must be set in the TestConfiguration");
}
if (string.IsNullOrEmpty(config.BuildProjectFramework))
{
throw new System.ArgumentException("BuildProjectFramework must be set in the TestConfiguration");
}

// The layout is how the current .NET Core SDK layouts the binaries out:
// Source Path: <DebuggeeSourceRoot>/<DebuggeeName>/[<DebuggeeName>]
// Binary Path: <DebuggeeBuildRoot>/bin/<DebuggeeName>/<TargetConfiguration>/<BuildProjectFramework>
Expand Down
4 changes: 4 additions & 0 deletions src/diagnostics/src/tests/CommonTestRunner/TestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public static async Task<TestRunner> Create(TestConfiguration config, ITestOutpu

// Get the full debuggee launch command line (includes the host if required)
string exePath = debuggeeConfig.BinaryExePath;
if (!File.Exists(exePath))
{
throw new FileNotFoundException($"Expected to find target executable at {exePath} but it didn't exist. Perhaps the path was improperly configured or a build/deployment error caused the file to be missing?");
}
string pipeName = null;

StringBuilder arguments = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,17 @@ private sealed class TestMetricsLogger : ICountersLogger
private readonly List<ExpectedCounter> _expectedCounters = new();
private Dictionary<ExpectedCounter, ICounterPayload> _metrics = new();
private readonly TaskCompletionSource<object> _foundExpectedCountersSource;
private readonly ITestOutputHelper _output;

public TestMetricsLogger(IEnumerable<ExpectedCounter> expectedCounters, TaskCompletionSource<object> foundExpectedCountersSource)
public TestMetricsLogger(IEnumerable<ExpectedCounter> expectedCounters, TaskCompletionSource<object> foundExpectedCountersSource, ITestOutputHelper output)
{
_foundExpectedCountersSource = foundExpectedCountersSource;
_expectedCounters = new(expectedCounters);
if (_expectedCounters.Count == 0)
{
foundExpectedCountersSource.SetResult(null);
}
_output = output;
}

public IEnumerable<ICounterPayload> Metrics => _metrics.Values;
Expand All @@ -90,17 +92,29 @@ public void Log(ICounterPayload payload)
ExpectedCounter expectedCounter = _expectedCounters.Find(c => c.MatchesCounterMetadata(payload.CounterMetadata));
if(expectedCounter != null)
{

_expectedCounters.Remove(expectedCounter);
_metrics.Add(expectedCounter, payload);

_output.WriteLine($"Found expected counter: {expectedCounter.ProviderName}/{expectedCounter.CounterName}. Counters remaining={_expectedCounters.Count}");
// Complete the task source if the last expected key was removed.
if (_expectedCounters.Count == 0)
{
_output.WriteLine($"All expected counters have been received. Signaling pipeline can exit.");
_foundExpectedCountersSource.TrySetResult(null);
}
}
else
{
_output.WriteLine($"Received additional counter event: {payload.CounterMetadata.ProviderName}/{payload.CounterMetadata.CounterName}");
}
}

public Task PipelineStarted(CancellationToken token) => Task.CompletedTask;
public Task PipelineStarted(CancellationToken token)
{
_output.WriteLine("Counters pipeline is running. Waiting to receive expected counters from tracee.");
return Task.CompletedTask;
}

public Task PipelineStopped(CancellationToken token) => Task.CompletedTask;
}
Expand All @@ -113,7 +127,7 @@ public async Task TestCounterEventPipeline(TestConfiguration config)

TaskCompletionSource<object> foundExpectedCountersSource = new(TaskCreationOptions.RunContinuationsAsynchronously);

TestMetricsLogger logger = new(expectedCounters.Select(name => new ExpectedCounter(expectedProvider, name)), foundExpectedCountersSource);
TestMetricsLogger logger = new(expectedCounters.Select(name => new ExpectedCounter(expectedProvider, name)), foundExpectedCountersSource, _output);

await using (TestRunner testRunner = await PipelineTestUtilities.StartProcess(config, "CounterRemoteTest", _output))
{
Expand Down Expand Up @@ -169,7 +183,7 @@ public async Task TestDuplicateNameMetrics(TestConfiguration config)
new ExpectedCounter(providerName, counterName, "MeterTag=two","InstrumentTag=B"),
];
TaskCompletionSource<object> foundExpectedCountersSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
TestMetricsLogger logger = new(expectedCounters, foundExpectedCountersSource);
TestMetricsLogger logger = new(expectedCounters, foundExpectedCountersSource, _output);

await using (TestRunner testRunner = await PipelineTestUtilities.StartProcess(config, "DuplicateNameMetrics", _output, testProcessTimeout: 3_000))
{
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy