File: System\Diagnostics\Process.Multiplexing.Unix.cs
Web Access
Project: src\src\libraries\System.Diagnostics.Process\src\System.Diagnostics.Process.csproj (System.Diagnostics.Process)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
 
namespace System.Diagnostics
{
    public partial class Process
    {
        private static SafePipeHandle GetSafeHandleFromStreamReader(StreamReader reader) => ((AnonymousPipeClientStream)reader.BaseStream).SafePipeHandle;
 
        /// <summary>
        /// Reads from both standard output and standard error pipes using Unix poll-based multiplexing
        /// with non-blocking reads.
        /// </summary>
        private static void ReadPipes(
            SafePipeHandle outputHandle,
            SafePipeHandle errorHandle,
            int timeoutMs,
            ref byte[] outputBuffer,
            ref int outputBytesRead,
            ref byte[] errorBuffer,
            ref int errorBytesRead)
        {
            int outputFd = outputHandle.DangerousGetHandle().ToInt32();
            int errorFd = errorHandle.DangerousGetHandle().ToInt32();
 
            if (Interop.Sys.Fcntl.DangerousSetIsNonBlocking(outputFd, 1) != 0 || Interop.Sys.Fcntl.DangerousSetIsNonBlocking(errorFd, 1) != 0)
            {
                throw new Win32Exception();
            }
 
            Span<Interop.PollEvent> pollFds = stackalloc Interop.PollEvent[2];
 
            long deadline = timeoutMs >= 0
                ? Environment.TickCount64 + timeoutMs
                : long.MaxValue;
 
            bool outputDone = false, errorDone = false;
            while (!outputDone || !errorDone)
            {
                int numFds = 0;
 
                int outputIndex = -1;
                int errorIndex = -1;
 
                if (!outputDone)
                {
                    outputIndex = numFds;
                    pollFds[numFds].FileDescriptor = outputFd;
                    pollFds[numFds].Events = Interop.PollEvents.POLLIN;
                    pollFds[numFds].TriggeredEvents = Interop.PollEvents.POLLNONE;
                    numFds++;
                }
 
                if (!errorDone)
                {
                    errorIndex = numFds;
                    pollFds[numFds].FileDescriptor = errorFd;
                    pollFds[numFds].Events = Interop.PollEvents.POLLIN;
                    pollFds[numFds].TriggeredEvents = Interop.PollEvents.POLLNONE;
                    numFds++;
                }
 
                int pollTimeout;
                if (!TryGetRemainingTimeout(deadline, timeoutMs, out pollTimeout))
                {
                    throw new TimeoutException();
                }
 
                unsafe
                {
                    uint triggered;
                    fixed (Interop.PollEvent* pPollFds = pollFds)
                    {
                        Interop.Error error = Interop.Sys.Poll(pPollFds, (uint)numFds, pollTimeout, &triggered);
                        if (error != Interop.Error.SUCCESS)
                        {
                            if (error == Interop.Error.EINTR)
                            {
                                // We don't re-issue the poll immediately because we need to check
                                // if we've already exceeded the overall timeout.
                                continue;
                            }
 
                            throw new Win32Exception(Interop.Sys.ConvertErrorPalToPlatform(error));
                        }
 
                        if (triggered == 0)
                        {
                            throw new TimeoutException();
                        }
                    }
                }
 
                for (int i = 0; i < numFds; i++)
                {
                    if (pollFds[i].TriggeredEvents == Interop.PollEvents.POLLNONE)
                    {
                        continue;
                    }
 
                    bool isError = i == errorIndex;
                    SafePipeHandle currentHandle = isError ? errorHandle : outputHandle;
                    ref byte[] currentBuffer = ref (isError ? ref errorBuffer : ref outputBuffer);
                    ref int currentBytesRead = ref (isError ? ref errorBytesRead : ref outputBytesRead);
                    ref bool currentDone = ref (isError ? ref errorDone : ref outputDone);
 
                    int bytesRead = ReadNonBlocking(currentHandle, currentBuffer, currentBytesRead);
                    if (bytesRead > 0)
                    {
                        currentBytesRead += bytesRead;
 
                        if (currentBytesRead == currentBuffer.Length)
                        {
                            RentLargerBuffer(ref currentBuffer, currentBytesRead);
                        }
                    }
                    else if (bytesRead == 0)
                    {
                        // EOF: pipe write end was closed.
                        currentDone = true;
                    }
                    // bytesRead < 0 means EAGAIN — nothing available yet, let poll retry.
                }
            }
        }
 
        /// <summary>
        /// Performs a non-blocking read from the given handle into the buffer starting at the specified offset.
        /// Returns the number of bytes read, 0 for EOF, or -1 for EAGAIN (nothing available yet).
        /// </summary>
        private static unsafe int ReadNonBlocking(SafePipeHandle handle, byte[] buffer, int offset)
        {
            fixed (byte* pBuffer = buffer)
            {
                int bytesRead = Interop.Sys.Read(handle, pBuffer + offset, buffer.Length - offset);
                if (bytesRead < 0)
                {
                    Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
                    if (errorInfo.Error == Interop.Error.EAGAIN)
                    {
                        return -1;
                    }
 
                    throw new Win32Exception(errorInfo.RawErrno);
                }
 
                return bytesRead;
            }
        }
    }
}