Skip to content

Commit 1d0b2be

Browse files
Add --show-child-io feature (#1968)
* Fix deadlock when redirecting large amounts of child process output, and add ability to not redirect child process behavior - Use a small set of tasks to consume and ignore arbitrary data passed through standard output/error - Add a new switch to dotnet-trace called --redirect-child-output which can be set to false to allow viewing data. (By default the option defaults to true, which may be confusing. I'd like some feedback on that. * Adjust dotnet-trace to add --show-child-output and improve exit code handling * Add testing * Update doc in repo * Fix issues identified in testing * Move logic which waited for process to exit to avoid premature Dispose operations * Code review feedback
1 parent d17826e commit 1d0b2be

File tree

11 files changed

+312
-35
lines changed

11 files changed

+312
-35
lines changed

diagnostics.sln

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Exten
202202
EndProject
203203
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.DebugServices.UnitTests", "src\tests\Microsoft.Diagnostics.DebugServices.UnitTests\Microsoft.Diagnostics.DebugServices.UnitTests.csproj", "{064BC7DD-D44C-400E-9215-7546E092AB98}"
204204
EndProject
205+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExitCodeTracee", "src\tests\ExitCodeTracee\ExitCodeTracee.csproj", "{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}"
206+
EndProject
205207
Global
206208
GlobalSection(SolutionConfigurationPlatforms) = preSolution
207209
Checked|Any CPU = Checked|Any CPU
@@ -1659,6 +1661,46 @@ Global
16591661
{064BC7DD-D44C-400E-9215-7546E092AB98}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
16601662
{064BC7DD-D44C-400E-9215-7546E092AB98}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
16611663
{064BC7DD-D44C-400E-9215-7546E092AB98}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
1664+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
1665+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|Any CPU.Build.0 = Debug|Any CPU
1666+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|ARM.ActiveCfg = Debug|Any CPU
1667+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|ARM.Build.0 = Debug|Any CPU
1668+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|ARM64.ActiveCfg = Debug|Any CPU
1669+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|ARM64.Build.0 = Debug|Any CPU
1670+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|x64.ActiveCfg = Debug|Any CPU
1671+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|x64.Build.0 = Debug|Any CPU
1672+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|x86.ActiveCfg = Debug|Any CPU
1673+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Checked|x86.Build.0 = Debug|Any CPU
1674+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1675+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
1676+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|ARM.ActiveCfg = Debug|Any CPU
1677+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|ARM.Build.0 = Debug|Any CPU
1678+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|ARM64.ActiveCfg = Debug|Any CPU
1679+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|ARM64.Build.0 = Debug|Any CPU
1680+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|x64.ActiveCfg = Debug|Any CPU
1681+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|x64.Build.0 = Debug|Any CPU
1682+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|x86.ActiveCfg = Debug|Any CPU
1683+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Debug|x86.Build.0 = Debug|Any CPU
1684+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
1685+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|Any CPU.Build.0 = Release|Any CPU
1686+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|ARM.ActiveCfg = Release|Any CPU
1687+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|ARM.Build.0 = Release|Any CPU
1688+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|ARM64.ActiveCfg = Release|Any CPU
1689+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|ARM64.Build.0 = Release|Any CPU
1690+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|x64.ActiveCfg = Release|Any CPU
1691+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|x64.Build.0 = Release|Any CPU
1692+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|x86.ActiveCfg = Release|Any CPU
1693+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.Release|x86.Build.0 = Release|Any CPU
1694+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
1695+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
1696+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|ARM.ActiveCfg = Release|Any CPU
1697+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|ARM.Build.0 = Release|Any CPU
1698+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|ARM64.ActiveCfg = Release|Any CPU
1699+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|ARM64.Build.0 = Release|Any CPU
1700+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
1701+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
1702+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
1703+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
16621704
EndGlobalSection
16631705
GlobalSection(SolutionProperties) = preSolution
16641706
HideSolutionNode = FALSE
@@ -1711,6 +1753,7 @@ Global
17111753
{EEC90A42-CDCD-4EE3-B47D-C109D604E7E2} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
17121754
{5FC66A16-41E9-4D22-A44C-FEBB7DCCAAF8} = {19FAB78C-3351-4911-8F0C-8C6056401740}
17131755
{064BC7DD-D44C-400E-9215-7546E092AB98} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
1756+
{61F73DD0-F346-4D7A-AB12-63415B4EEEE1} = {03479E19-3F18-49A6-910A-F5041E27E7C0}
17141757
EndGlobalSection
17151758
GlobalSection(ExtensibilityGlobals) = postSolution
17161759
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}

documentation/design-docs/dotnet-tools.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ COLLECT
311311
[--providers <list-of-comma-separated-providers>]
312312
[--format <trace-file-format>]
313313

314-
Collects a diagnostic trace from a currently running process
314+
Collects a diagnostic trace from a currently running process or launch a child process and trace it. Append -- to the collect command to instruct the tool to run a command and trace it immediately.
315315

316316
-p, --process-id
317317
The process to collect the trace from
@@ -351,6 +351,8 @@ COLLECT
351351
--format
352352
The format of the output trace file. The default value is nettrace.
353353

354+
--show-child-io
355+
Shows the input and output streams of a launched child process in the current console.
354356

355357
Examples:
356358

documentation/dotnet-trace-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ Press <Enter> or <Ctrl+C> to exit...
8787
You can stop collecting the trace by pressing `<Enter>` or `<Ctrl + C>` key. Doing this will also exit `hello.exe`.
8888

8989
### NOTE
90-
* Launching `hello.exe` via dotnet-trace will make its input/output to be redirected and you won't be able to interact with its stdin/stdout.
90+
* Launching `hello.exe` via dotnet-trace will redirect its input/output and you will not be able to interact with it on the console by default. Use the --show-child-io switch to interact with its stdin/stdout.
9191

9292
* Exiting the tool via CTRL+C or SIGTERM will safely end both the tool and the child process.
9393

src/Tools/Common/Commands/Utils.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,35 @@ public static int FindProcessIdWithName(string name)
4242
}
4343

4444
/// <summary>
45-
/// A helper method for validating --process-id, --name, --diagnostic-port options for collect and monitor commands.
45+
/// A helper method for validating --process-id, --name, --diagnostic-port options for collect with child process commands.
46+
/// None of these options can be specified, so it checks for them and prints the appropriate error message.
47+
/// </summary>
48+
/// <param name="processId">process ID</param>
49+
/// <param name="name">name</param>
50+
/// <param name="port">port</param>
51+
/// <returns></returns>
52+
public static bool ValidateArgumentsForChildProcess(int processId, string name, string port)
53+
{
54+
if (processId != 0 && name != null && !string.IsNullOrEmpty(port))
55+
{
56+
Console.WriteLine("None of the --name, --process-id, or --diagnostic-port options may be specified when launching a child process.");
57+
return false;
58+
}
59+
60+
return true;
61+
}
62+
63+
/// <summary>
64+
/// A helper method for validating --process-id, --name, --diagnostic-port options for collect commands.
4665
/// Only one of these options can be specified, so it checks for duplicate options specified and if there is
4766
/// such duplication, it prints the appropriate error message.
4867
/// </summary>
4968
/// <param name="processId">process ID</param>
5069
/// <param name="name">name</param>
5170
/// <param name="port">port</param>
71+
/// <param name="resolvedProcessId">resolvedProcessId</param>
5272
/// <returns></returns>
53-
public static bool ValidateArguments(int processId, string name, string port, out int resolvedProcessId)
73+
public static bool ValidateArgumentsForAttach(int processId, string name, string port, out int resolvedProcessId)
5474
{
5575
resolvedProcessId = -1;
5676
if (processId == 0 && name == null && string.IsNullOrEmpty(port))
@@ -97,6 +117,11 @@ public static bool ValidateArguments(int processId, string name, string port, ou
97117
return false;
98118
}
99119
}
120+
else if (processId == 0)
121+
{
122+
Console.WriteLine("One of the --name, --process-id, or --diagnostic-port options must be specified when attaching to a process.");
123+
return false;
124+
}
100125
resolvedProcessId = processId;
101126
return true;
102127
}

src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,19 @@ public Process ChildProc
8383
return _childProc;
8484
}
8585
}
86-
public bool Start(string diagnosticTransportName, CancellationToken ct)
86+
public bool Start( string diagnosticTransportName, CancellationToken ct, bool showChildIO, bool printLaunchCommand)
8787
{
8888
_childProc.StartInfo.UseShellExecute = false;
89-
_childProc.StartInfo.RedirectStandardOutput = true;
90-
_childProc.StartInfo.RedirectStandardError = true;
91-
_childProc.StartInfo.RedirectStandardInput = true;
89+
_childProc.StartInfo.RedirectStandardOutput = !showChildIO;
90+
_childProc.StartInfo.RedirectStandardError = !showChildIO;
91+
_childProc.StartInfo.RedirectStandardInput = !showChildIO;
9292
_childProc.StartInfo.Environment.Add("DOTNET_DiagnosticPorts", $"{diagnosticTransportName}");
9393
try
9494
{
95+
if (printLaunchCommand)
96+
{
97+
Console.WriteLine($"Launching: {_childProc.StartInfo.FileName} {_childProc.StartInfo.Arguments}");
98+
}
9599
_childProc.Start();
96100
}
97101
catch (Exception e)
@@ -100,8 +104,11 @@ public bool Start(string diagnosticTransportName, CancellationToken ct)
100104
Console.WriteLine(e.ToString());
101105
return false;
102106
}
103-
_stdOutTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardOutput, ct);
104-
_stdErrTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardError, ct);
107+
if (!showChildIO)
108+
{
109+
_stdOutTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardOutput, ct);
110+
_stdErrTask = ReadAndIgnoreAllStreamAsync(_childProc.StandardError, ct);
111+
}
105112

106113
return true;
107114
}
@@ -183,7 +190,7 @@ public DiagnosticsClientBuilder(string toolName, int timeoutInSec)
183190
_timeoutInSec = timeoutInSec;
184191
}
185192

186-
public async Task<DiagnosticsClientHolder> Build(CancellationToken ct, int processId, string portName)
193+
public async Task<DiagnosticsClientHolder> Build(CancellationToken ct, int processId, string portName, bool showChildIO, bool printLaunchCommand)
187194
{
188195
if (ProcessLauncher.Launcher.HasChildProc)
189196
{
@@ -193,9 +200,9 @@ public async Task<DiagnosticsClientHolder> Build(CancellationToken ct, int proce
193200
server.Start();
194201

195202
// Start the child proc
196-
if (!ProcessLauncher.Launcher.Start(diagnosticTransportName, ct))
203+
if (!ProcessLauncher.Launcher.Start(diagnosticTransportName, ct, showChildIO, printLaunchCommand))
197204
{
198-
throw new InvalidOperationException($"Failed to start {ProcessLauncher.Launcher.ChildProc.ProcessName}.");
205+
throw new InvalidOperationException($"Failed to start '{ProcessLauncher.Launcher.ChildProc.StartInfo.FileName} {ProcessLauncher.Launcher.ChildProc.StartInfo.Arguments}'.");
199206
}
200207
IpcEndpointInfo endpointInfo;
201208
try

src/Tools/dotnet-counters/CounterMonitor.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ private void StopMonitor()
9292

9393
public async Task<int> Monitor(CancellationToken ct, List<string> counter_list, string counters, IConsole console, int processId, int refreshInterval, string name, string diagnosticPort)
9494
{
95-
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArguments(processId, name, diagnosticPort, out _processId))
95+
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
9696
{
9797
return 0;
9898
}
9999

100100
DiagnosticsClientBuilder builder = new DiagnosticsClientBuilder("dotnet-counters", 10);
101-
using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort))
101+
using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort, showChildIO: false, printLaunchCommand: false))
102102
{
103103
try
104104
{
@@ -130,12 +130,12 @@ public async Task<int> Monitor(CancellationToken ct, List<string> counter_list,
130130

131131
public async Task<int> Collect(CancellationToken ct, List<string> counter_list, string counters, IConsole console, int processId, int refreshInterval, CountersExportFormat format, string output, string name, string diagnosticPort)
132132
{
133-
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArguments(processId, name, diagnosticPort, out _processId))
133+
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
134134
{
135135
return 0;
136136
}
137137
DiagnosticsClientBuilder builder = new DiagnosticsClientBuilder("dotnet-counters", 10);
138-
using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort))
138+
using (DiagnosticsClientHolder holder = await builder.Build(ct, _processId, diagnosticPort, showChildIO: false, printLaunchCommand: false))
139139
{
140140
try
141141
{

0 commit comments

Comments
 (0)