Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6946997
Add Process.ReadAllText and ReadAllBytes with platform-specific multi…
Copilot Apr 12, 2026
c2dceaf
Fix fileOffset values and refactor to unified ReadPipes (WIP - nullab…
Copilot Apr 12, 2026
d518461
Rewrite Windows ReadPipes to use overlapped IO with wait handles inst…
Copilot Apr 12, 2026
4eb1e88
Fix code review: make RentLargerBuffer private, fix fully-qualified W…
Copilot Apr 12, 2026
0986690
Address review feedback: require both stdout/stderr redirected, add h…
Copilot Apr 12, 2026
df0e65a
Address round 2 feedback: extract ValidateReadAllState helper, make W…
Copilot Apr 12, 2026
5d6e9c0
address my own feedback:
adamsitnik Apr 13, 2026
99ef166
Apply suggestions from code review
adamsitnik Apr 13, 2026
70df3b1
fix the build
adamsitnik Apr 13, 2026
474a484
Fix test missing else, use ToTimeoutMilliseconds for validation, fix …
Copilot Apr 13, 2026
9775b5d
Require StreamReadMode.Undefined for ReadAll*, rename test, add sync …
Copilot Apr 13, 2026
1d30ed2
Address feedback: Win32Exception fix, ref assembly order, AllocateOve…
Copilot Apr 13, 2026
77dceb0
Add using to Process in ReadAllBytes_ReadsBinaryDataWithNullBytes test
Copilot Apr 13, 2026
9896de0
Fix RentLargerBuffer infinite loop at Array.MaxLength, use Assert.Equ…
Copilot Apr 13, 2026
c3bd6bc
address my own feedback
adamsitnik Apr 13, 2026
ce6b594
Merge branch 'main' into copilot/implement-readalltext-and-readallbytes
adamsitnik Apr 13, 2026
e8cbe82
Address jkotas and tmds feedback: PinnedGCHandle, RentLargerBuffer im…
Copilot Apr 14, 2026
6ecc0b9
Flip WaitHandle order to prioritize stderr, add Raymond Chen blog links
Copilot Apr 14, 2026
8f7c3c8
Remove Raymond Chen blog links from CancelPendingIOIfNeeded — keep on…
Copilot Apr 14, 2026
c92f673
Use non-blocking IO on Unix: DangerousSetIsNonBlocking + Interop.Sys.…
Copilot Apr 14, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ public void Kill() { }
public void Kill(bool entireProcessTree) { }
public static void LeaveDebugMode() { }
protected void OnExited() { }
public (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; }
public (string StandardOutput, string StandardError) ReadAllText(System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; }
public void Refresh() { }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<Compile Include="System\Diagnostics\AsyncStreamReader.cs" />
<Compile Include="System\Diagnostics\DataReceivedEventArgs.cs" />
<Compile Include="System\Diagnostics\Process.cs" />
<Compile Include="System\Diagnostics\Process.Multiplexing.cs" />
<Compile Include="System\Diagnostics\ProcessExitStatus.cs" />
<Compile Include="System\Diagnostics\ProcessInfo.cs" />
<Compile Include="System\Diagnostics\ProcessModule.cs" />
Expand Down Expand Up @@ -224,6 +225,13 @@
<Compile Include="Microsoft\Win32\SafeHandles\SafeProcessHandle.Windows.cs" />
<Compile Include="System\Diagnostics\PerformanceCounterLib.cs" />
<Compile Include="System\Diagnostics\Process.Windows.cs" />
<Compile Include="System\Diagnostics\Process.Multiplexing.Windows.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.ReadFile_SafeHandle_NativeOverlapped.cs"
Link="Common\Interop\Windows\Kernel32\Interop.ReadFile_SafeHandle_NativeOverlapped.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetOverlappedResult.cs"
Link="Common\Interop\Windows\Kernel32\Interop.GetOverlappedResult.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CancelIoEx.cs"
Link="Common\Interop\Windows\Kernel32\Interop.CancelIoEx.cs" />
<Compile Include="System\Diagnostics\ProcessManager.Windows.cs" />
<Compile Include="System\Diagnostics\ProcessStartInfo.Windows.cs" />
<Compile Include="System\Diagnostics\ProcessThread.Windows.cs" />
Expand All @@ -236,6 +244,7 @@
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows'">
<Compile Include="Microsoft\Win32\SafeHandles\SafeProcessHandle.Unix.cs" />
<Compile Include="System\Diagnostics\Process.Unix.cs" />
<Compile Include="System\Diagnostics\Process.Multiplexing.Unix.cs" />
<Compile Include="System\Diagnostics\ProcessManager.Unix.cs" />
<Compile Include="System\Diagnostics\ProcessThread.Unix.cs" />
<Compile Include="System\Diagnostics\ProcessStartInfo.Unix.cs" />
Expand All @@ -246,6 +255,14 @@
Link="Common\Interop\Unix\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
Link="Common\Interop\Unix\Interop.Errors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.Poll.Structs.cs"
Link="Common\Interop\Unix\Interop.Poll.Structs.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Poll.cs"
Link="Common\Interop\Unix\System.Native\Interop.Poll.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Fcntl.cs"
Link="Common\Interop\Unix\System.Native\Interop.Fcntl.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Read.cs"
Link="Common\Interop\Unix\System.Native\Interop.Read.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Close.cs"
Link="Common\Interop\Unix\Interop.Close.cs" />
<Compile Include="$(CommonPath)Interop\Unix\Interop.DefaultPathBufferSize.cs"
Expand Down Expand Up @@ -406,6 +423,7 @@
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\src\System.Runtime.InteropServices.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Text.Encoding.Extensions\src\System.Text.Encoding.Extensions.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading\src\System.Threading.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading.Overlapped\src\System.Threading.Overlapped.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading.Thread\src\System.Threading.Thread.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Threading.ThreadPool\src\System.Threading.ThreadPool.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// 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.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace System.Diagnostics
{
public partial class Process
{
/// <summary>
/// Reads from both standard output and standard error pipes using Unix poll-based multiplexing
/// with non-blocking reads.
/// </summary>
private static void ReadPipes(
SafeFileHandle outputHandle,
SafeFileHandle 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();
Comment thread
adamsitnik marked this conversation as resolved.

if (Interop.Sys.Fcntl.DangerousSetIsNonBlocking((IntPtr)outputFd, 1) != 0 || Interop.Sys.Fcntl.DangerousSetIsNonBlocking((IntPtr)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);
Comment thread
adamsitnik marked this conversation as resolved.
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;
}
Comment thread
adamsitnik marked this conversation as resolved.

bool isError = i == errorIndex;
SafeFileHandle 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(SafeFileHandle 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;
}
}
}
}
Loading
Loading