-
Notifications
You must be signed in to change notification settings - Fork 665
Fix process crash when testing Stderr #1449
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
base: main
Are you sure you want to change the base?
Changes from all commits
69a22a2
ee120f6
2539d5c
82623d1
db2baaa
c1d3675
8f6e91f
fa6e7b3
ccfd685
5cae6fb
0647e71
a698b02
8ad61e5
bc10e41
289e6b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,6 +59,7 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = | |
|
|
||
| Process? process = null; | ||
| bool processStarted = false; | ||
| DataReceivedEventHandler? errorHandler = null; | ||
|
|
||
| string command = _options.Command; | ||
| IList<string>? arguments = _options.Arguments; | ||
|
|
@@ -136,7 +137,7 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = | |
| // few lines in a rolling log for use in exceptions. | ||
| const int MaxStderrLength = 10; // keep the last 10 lines of stderr | ||
| Queue<string> stderrRollingLog = new(MaxStderrLength); | ||
| process.ErrorDataReceived += (sender, args) => | ||
| errorHandler = (sender, args) => | ||
| { | ||
| string? data = args.Data; | ||
| if (data is not null) | ||
|
|
@@ -151,11 +152,22 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = | |
| stderrRollingLog.Enqueue(data); | ||
| } | ||
|
|
||
| _options.StandardErrorLines?.Invoke(data); | ||
| try | ||
| { | ||
| _options.StandardErrorLines?.Invoke(data); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Prevent exceptions in the user callback from propagating | ||
| // to the background thread that dispatches ErrorDataReceived, | ||
| // which would crash the process. | ||
| LogStderrCallbackFailed(logger, endpointName, ex); | ||
| } | ||
|
|
||
| LogReadStderr(logger, endpointName, data); | ||
| } | ||
| }; | ||
| process.ErrorDataReceived += errorHandler; | ||
|
|
||
| // We need both stdin and stdout to use a no-BOM UTF-8 encoding. On .NET Core, | ||
| // we can use ProcessStartInfo.StandardOutputEncoding/StandardInputEncoding, but | ||
|
|
@@ -193,14 +205,19 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = | |
|
|
||
| process.BeginErrorReadLine(); | ||
|
|
||
| return new StdioClientSessionTransport(_options, process, endpointName, stderrRollingLog, _loggerFactory); | ||
| return new StdioClientSessionTransport(_options, process, endpointName, stderrRollingLog, errorHandler, _loggerFactory); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| LogTransportConnectFailed(logger, endpointName, ex); | ||
|
|
||
| try | ||
| { | ||
| if (process is not null && errorHandler is not null) | ||
| { | ||
| process.ErrorDataReceived -= errorHandler; | ||
| } | ||
|
|
||
| DisposeProcess(process, processStarted, _options.ShutdownTimeout); | ||
| } | ||
| catch (Exception ex2) | ||
|
|
@@ -228,18 +245,6 @@ internal static void DisposeProcess( | |
| process.KillTree(shutdownTimeout); | ||
| } | ||
|
|
||
| // Ensure all redirected stderr/stdout events have been dispatched | ||
| // before disposing. Only the no-arg WaitForExit() guarantees this; | ||
| // WaitForExit(int) (as used by KillTree) does not. | ||
| // This should not hang: either the process already exited on its own | ||
| // (no child processes holding handles), or KillTree killed the entire | ||
| // process tree. If it does take too long, the test infrastructure's | ||
| // own timeout will catch it. | ||
| if (!processRunning && HasExited(process)) | ||
| { | ||
| process.WaitForExit(); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't we now possibly miss some error messages being reported over stderr from the child process?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The In the The reason for removing it from
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
WaitForExit(timeout) doesn't wait for EOF of the streams though, right?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right — I checked the .NET runtime source for On .NET Framework there's no |
||
|
|
||
| // Invoke the callback while the process handle is still valid, | ||
| // e.g. to read ExitCode before Dispose() invalidates it. | ||
| beforeDispose?.Invoke(); | ||
|
|
@@ -299,6 +304,9 @@ private static string EscapeArgumentString(string argument) => | |
| [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} received stderr log: '{Data}'.")] | ||
| private static partial void LogReadStderr(ILogger logger, string endpointName, string data); | ||
|
|
||
| [LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} StandardErrorLines callback failed.")] | ||
| private static partial void LogStderrCallbackFailed(ILogger logger, string endpointName, Exception exception); | ||
|
|
||
| [LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} started server process with PID {ProcessId}.")] | ||
| private static partial void LogTransportProcessStarted(ILogger logger, string endpointName, int processId); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.