Ignoring stdout of child process, dangerous?

夙愿已清 提交于 2020-06-16 17:01:26

问题


I have an application which starts 1-45 processes (job dependent) and communicates with them via named pipes. I'm only interested in the stderr of the child processes and have no interest in redirecting the stdout of the processes.

I have written code that does exactly this, I redirect stderr and not stdout and everything works as expected. And when I say that I mean, I get the redirected stderr and I don't see any stdout appearing on the parent processes console nor do I get any deadlocks due to not redirecting stdout.

My concern here is, where is the stdout of the child processes going? The processes tend to dump a lot of text to stdout and I'm afraid they're filling up some internal buffer which is going to cause the child processes to block when writing to stdout.

In the past I also redirected stdout and just dumped (did nothing) the data provided from the async event. But doing this had a severe consequence. As stated above I communicate via named pipes with the child processes. What I noticed is when I redirect stdout it hammers the performance of the parent processes write operations over the named pipes.

When not redirecting stdout the write times are usually < 1ms but after redirecting stdout the times jump to > 4s...

My question is, is it safe to only redirect stderr?

Below is part of the code I use to wrap the Process. I also use that well established extension method for awaiting the process to complete. I didn't post that but I can if you're not familiar with it.

public class ChildProcess
{
    public async Task StartProcessAsync( string executablePath, CancellationToken token = default )
    {
        if ( !File.Exists( executablePath ) )
            throw new FileNotFoundException( "The specified rip executable path does not exist", executablePath );

        using var process = new Process( );          
        process.StartInfo.FileName = executablePath;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.ErrorDialog = false;
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardError = true;

        await StartProcess( process, StdErrorOutput, token ).ConfigureAwait( false );

        static void StdErrorOutput( object sender, DataReceivedEventArgs args )
        {
            if ( string.IsNullOrWhiteSpace( args.Data ) ) return;
        }
    }

    private Task StartProcess( Process process,
        DataReceivedEventHandler? stderr, CancellationToken token = default )
    {
        return Task.Run( async ( ) =>
        {
            try
            {
                if ( stderr is object )
                    process.ErrorDataReceived += stderr;

                if ( !process.Start( ) )
                    throw new ApplicationException( $"Failed to start '{process.StartInfo.FileName}'" );

                if ( stderr is object )
                    process.BeginErrorReadLine( );

                await process.WaitForExitAsync( token ).ConfigureAwait( false );
            }
            finally
            {
                SafeKill( );

                if ( stderr is object )
                    process.ErrorDataReceived -= stderr;
            }

            void SafeKill( )
            {
                try
                {
                    if ( !process.HasExited )
                    {
                        process.Refresh( );

                        if ( stderr is object )
                            process.CancelErrorRead( );

                        process.Kill( );
                    }
                }
                catch ( Exception ex )
                {
                    Console.WriteLine( ex );
                }
            }

        } );
    }
}

Edit

After reading the comment left by Eryk Sun I decided to dig into the Process source code to see if I could figure out what's happening. Mainly I was interested in seeing how the STARTUPINFO structure was populated when only specifying redirection of one of the std streams.

I removed a bunch of code I wasn't interest in. From what I can tell, if I set RedirectStandardError to true and specify RedirectStandardOutput to false the STARTIPINFO structures hStdOutput member is set to stdout. This results in the StandardOutput property on the Process class to be null.

If RedirectStandardOutput was set to true the output from the child process is redirected through a pipe. A StreamReader instance is then created and assigned to StandardOutput. The StreamReader then reads from the pipe created to attain the output from the child process.

So if the HANDLE is being set to stdout when RedirectStandardOutput is set to false, why do I not see the output on the parent processes console window? Where is the output going?

System.Diagnostics.Process.StartWithCreateProcess

source

SafeFileHandle? childInputPipeHandle = null;
SafeFileHandle? parentOutputPipeHandle = null;

if (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError)
{
    if (startInfo.RedirectStandardOutput)
    {
        CreatePipe(out parentOutputPipeHandle, out childOutputPipeHandle, false);
    }
    else
    {
         childOutputPipeHandle = new SafeFileHandle(Interop.Kernel32.GetStdHandle(Interop.Kernel32.HandleTypes.STD_OUTPUT_HANDLE), false);
    }

    startupInfo.hStdOutput = childOutputPipeHandle.DangerousGetHandle();
    startupInfo.dwFlags = Interop.Advapi32.StartupInfoOptions.STARTF_USESTDHANDLES;
}

/* Code starting the process removed */

if (startInfo.RedirectStandardOutput)
{
    Encoding enc = startInfo.StandardOutputEncoding ?? GetEncoding((int)Interop.Kernel32.GetConsoleOutputCP());
    _standardOutput = new StreamReader(new FileStream(parentOutputPipeHandle!, FileAccess.Read, 4096, false), enc, true, 4096);
 }

来源:https://stackoverflow.com/questions/61891684/ignoring-stdout-of-child-process-dangerous

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!