Skip to content

Commit c92f673

Browse files
Copilotadamsitnik
andauthored
Use non-blocking IO on Unix: DangerousSetIsNonBlocking + Interop.Sys.Read with EAGAIN handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/2646b5c6-b0ae-4ae3-99ca-064187661e73 Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
1 parent 8f7c3c8 commit c92f673

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@
259259
Link="Common\Interop\Unix\Interop.Poll.Structs.cs" />
260260
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Poll.cs"
261261
Link="Common\Interop\Unix\System.Native\Interop.Poll.cs" />
262+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Fcntl.cs"
263+
Link="Common\Interop\Unix\System.Native\Interop.Fcntl.cs" />
264+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Read.cs"
265+
Link="Common\Interop\Unix\System.Native\Interop.Read.cs" />
262266
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Close.cs"
263267
Link="Common\Interop\Unix\Interop.Close.cs" />
264268
<Compile Include="$(CommonPath)Interop\Unix\Interop.DefaultPathBufferSize.cs"

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Multiplexing.Unix.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.ComponentModel;
5-
using System.IO;
65
using System.Runtime.InteropServices;
76
using Microsoft.Win32.SafeHandles;
87

@@ -11,7 +10,8 @@ namespace System.Diagnostics
1110
public partial class Process
1211
{
1312
/// <summary>
14-
/// Reads from both standard output and standard error pipes using Unix poll-based multiplexing.
13+
/// Reads from both standard output and standard error pipes using Unix poll-based multiplexing
14+
/// with non-blocking reads.
1515
/// </summary>
1616
private static void ReadPipes(
1717
SafeFileHandle outputHandle,
@@ -25,6 +25,11 @@ private static void ReadPipes(
2525
int outputFd = outputHandle.DangerousGetHandle().ToInt32();
2626
int errorFd = errorHandle.DangerousGetHandle().ToInt32();
2727

28+
if (Interop.Sys.Fcntl.DangerousSetIsNonBlocking((IntPtr)outputFd, 1) != 0 || Interop.Sys.Fcntl.DangerousSetIsNonBlocking((IntPtr)errorFd, 1) != 0)
29+
{
30+
throw new Win32Exception();
31+
}
32+
2833
Span<Interop.PollEvent> pollFds = stackalloc Interop.PollEvent[2];
2934

3035
long deadline = timeoutMs >= 0
@@ -101,7 +106,7 @@ private static void ReadPipes(
101106
ref int currentBytesRead = ref (isError ? ref errorBytesRead : ref outputBytesRead);
102107
ref bool currentDone = ref (isError ? ref errorDone : ref outputDone);
103108

104-
int bytesRead = RandomAccess.Read(currentHandle, currentBuffer.AsSpan(currentBytesRead), fileOffset: 0);
109+
int bytesRead = ReadNonBlocking(currentHandle, currentBuffer, currentBytesRead);
105110
if (bytesRead > 0)
106111
{
107112
currentBytesRead += bytesRead;
@@ -111,11 +116,37 @@ private static void ReadPipes(
111116
RentLargerBuffer(ref currentBuffer, currentBytesRead);
112117
}
113118
}
114-
else
119+
else if (bytesRead == 0)
115120
{
121+
// EOF: pipe write end was closed.
116122
currentDone = true;
117123
}
124+
// bytesRead < 0 means EAGAIN — nothing available yet, let poll retry.
125+
}
126+
}
127+
}
128+
129+
/// <summary>
130+
/// Performs a non-blocking read from the given handle into the buffer starting at the specified offset.
131+
/// Returns the number of bytes read, 0 for EOF, or -1 for EAGAIN (nothing available yet).
132+
/// </summary>
133+
private static unsafe int ReadNonBlocking(SafeFileHandle handle, byte[] buffer, int offset)
134+
{
135+
fixed (byte* pBuffer = buffer)
136+
{
137+
int bytesRead = Interop.Sys.Read(handle, pBuffer + offset, buffer.Length - offset);
138+
if (bytesRead < 0)
139+
{
140+
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
141+
if (errorInfo.Error == Interop.Error.EAGAIN)
142+
{
143+
return -1;
144+
}
145+
146+
throw new Win32Exception(errorInfo.RawErrno);
118147
}
148+
149+
return bytesRead;
119150
}
120151
}
121152
}

0 commit comments

Comments
 (0)