Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 6febde9

Browse files
committed
Handle EAGAIN in Console.Write (#23539)
If stdout/stderr is configured as a non-blocking file descriptor, Console.Write{Line} may fail if the descriptor is full and would block. With this commit, we instead poll in that case waiting for the ability to write and then retry.
1 parent fe0e443 commit 6febde9

File tree

4 files changed

+68
-3
lines changed

4 files changed

+68
-3
lines changed

src/System.Console/src/System.Console.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@
232232
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.OpenFlags.cs">
233233
<Link>Common\Interop\Unix\Interop.OpenFlags.cs</Link>
234234
</Compile>
235+
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Poll.cs">
236+
<Link>Common\Interop\Unix\Interop.Poll.cs</Link>
237+
</Compile>
235238
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetEUid.cs">
236239
<Link>Common\Interop\Unix\Interop.GetEUid.cs</Link>
237240
</Compile>

src/System.Console/src/System/ConsolePal.Unix.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,17 @@ private static unsafe void Write(SafeFileHandle fd, byte* bufPtr, int count)
977977
// that ended, so simply pretend we were successful.
978978
return;
979979
}
980+
else if (errorInfo.Error == Interop.Error.EAGAIN) // aka EWOULDBLOCK
981+
{
982+
// May happen if the file handle is configured as non-blocking.
983+
// In that case, we need to wait to be able to write and then
984+
// try again. We poll, but don't actually care about the result,
985+
// only the blocking behavior, and thus ignore any poll errors
986+
// and loop around to do another write (which may correctly fail
987+
// if something else has gone wrong).
988+
Interop.Sys.Poll(fd, Interop.Sys.PollEvents.POLLOUT, Timeout.Infinite, out Interop.Sys.PollEvents triggered);
989+
continue;
990+
}
980991
else
981992
{
982993
// Something else... fail.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using Xunit;
8+
9+
namespace System.Tests
10+
{
11+
public partial class NonStandardConfigurationTests : RemoteExecutorTestBase
12+
{
13+
[PlatformSpecific(TestPlatforms.AnyUnix)] // Uses P/Invokes
14+
[Fact]
15+
public void NonBlockingStdout_AllDataReceived()
16+
{
17+
RemoteInvokeHandle remote = RemoteInvoke(() =>
18+
{
19+
char[] data = Enumerable.Repeat('a', 1024).ToArray();
20+
21+
const int StdoutFd = 1;
22+
Assert.Equal(0, Interop.Sys.Fcntl.DangerousSetIsNonBlocking((IntPtr)StdoutFd, 1));
23+
24+
for (int i = 0; i < 10_000; i++)
25+
{
26+
Console.Write(data);
27+
}
28+
29+
return SuccessExitCode;
30+
}, new RemoteInvokeOptions { StartInfo = new ProcessStartInfo() { RedirectStandardOutput = true } });
31+
32+
using (remote)
33+
{
34+
Assert.Equal(
35+
new string('a', 1024 * 10_000),
36+
remote.Process.StandardOutput.ReadToEnd());
37+
}
38+
}
39+
}
40+
}

src/System.Console/tests/System.Console.Tests.csproj

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
<Link>Common\tests\System\PlatformDetection.cs</Link>
1616
</Compile>
1717
<Compile Include="CancelKeyPress.cs" />
18-
<Compile Include="CancelKeyPress.Unix.cs" Condition="'$(TargetsWindows)' != 'true'" />
1918
<Compile Include="Helpers.cs" />
2019
<Compile Include="ReadAndWrite.cs" />
2120
<Compile Include="ConsoleKeyInfoTests.cs" />
@@ -25,7 +24,6 @@
2524
<Compile Include="SetOut.cs" />
2625
<Compile Include="NegativeTesting.cs" />
2726
<Compile Include="ConsoleEncoding.cs" />
28-
<Compile Include="ConsoleEncoding.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
2927
<Compile Include="SyncTextReader.cs" />
3028
<Compile Include="SyncTextWriter.cs" />
3129
<Compile Include="Timeout.cs" />
@@ -51,11 +49,24 @@
5149
</Compile>
5250
<Compile Include="WindowAndCursorProps.cs" />
5351
</ItemGroup>
52+
<ItemGroup Condition="'$(TargetsWindows)' == 'true'" >
53+
<Compile Include="ConsoleEncoding.Windows.cs" />
54+
</ItemGroup>
55+
<ItemGroup Condition="'$(TargetsWindows)' != 'true'" >
56+
<Compile Include="CancelKeyPress.Unix.cs" />
57+
<Compile Include="NonStandardConfiguration.Unix.cs" />
58+
<Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs">
59+
<Link>Common\Interop\Windows\Interop.Libraries.cs</Link>
60+
</Compile>
61+
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Fcntl.cs">
62+
<Link>Interop\Unix\System.Native\Interop.Fcntl.cs</Link>
63+
</Compile>
64+
</ItemGroup>
5465
<ItemGroup>
5566
<ProjectReference Include="$(CommonTestPath)\System\Diagnostics\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj">
5667
<Project>{69e46a6f-9966-45a5-8945-2559fe337827}</Project>
5768
<Name>RemoteExecutorConsoleApp</Name>
5869
</ProjectReference>
5970
</ItemGroup>
6071
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
61-
</Project>
72+
</Project>

0 commit comments

Comments
 (0)