Skip to content

Commit

Permalink
Handle EAGAIN in Console.Write (dotnet#23539)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stephentoub committed Aug 31, 2017
1 parent fe0e443 commit 6febde9
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/System.Console/src/System.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.OpenFlags.cs">
<Link>Common\Interop\Unix\Interop.OpenFlags.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Poll.cs">
<Link>Common\Interop\Unix\Interop.Poll.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetEUid.cs">
<Link>Common\Interop\Unix\Interop.GetEUid.cs</Link>
</Compile>
Expand Down
11 changes: 11 additions & 0 deletions src/System.Console/src/System/ConsolePal.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,17 @@ private static unsafe void Write(SafeFileHandle fd, byte* bufPtr, int count)
// that ended, so simply pretend we were successful.
return;
}
else if (errorInfo.Error == Interop.Error.EAGAIN) // aka EWOULDBLOCK
{
// May happen if the file handle is configured as non-blocking.
// In that case, we need to wait to be able to write and then
// try again. We poll, but don't actually care about the result,
// only the blocking behavior, and thus ignore any poll errors
// and loop around to do another write (which may correctly fail
// if something else has gone wrong).
Interop.Sys.Poll(fd, Interop.Sys.PollEvents.POLLOUT, Timeout.Infinite, out Interop.Sys.PollEvents triggered);
continue;
}
else
{
// Something else... fail.
Expand Down
40 changes: 40 additions & 0 deletions src/System.Console/tests/NonStandardConfiguration.Unix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Linq;
using Xunit;

namespace System.Tests
{
public partial class NonStandardConfigurationTests : RemoteExecutorTestBase
{
[PlatformSpecific(TestPlatforms.AnyUnix)] // Uses P/Invokes
[Fact]
public void NonBlockingStdout_AllDataReceived()
{
RemoteInvokeHandle remote = RemoteInvoke(() =>
{
char[] data = Enumerable.Repeat('a', 1024).ToArray();
const int StdoutFd = 1;
Assert.Equal(0, Interop.Sys.Fcntl.DangerousSetIsNonBlocking((IntPtr)StdoutFd, 1));
for (int i = 0; i < 10_000; i++)
{
Console.Write(data);
}
return SuccessExitCode;
}, new RemoteInvokeOptions { StartInfo = new ProcessStartInfo() { RedirectStandardOutput = true } });

using (remote)
{
Assert.Equal(
new string('a', 1024 * 10_000),
remote.Process.StandardOutput.ReadToEnd());
}
}
}
}
17 changes: 14 additions & 3 deletions src/System.Console/tests/System.Console.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<Link>Common\tests\System\PlatformDetection.cs</Link>
</Compile>
<Compile Include="CancelKeyPress.cs" />
<Compile Include="CancelKeyPress.Unix.cs" Condition="'$(TargetsWindows)' != 'true'" />
<Compile Include="Helpers.cs" />
<Compile Include="ReadAndWrite.cs" />
<Compile Include="ConsoleKeyInfoTests.cs" />
Expand All @@ -25,7 +24,6 @@
<Compile Include="SetOut.cs" />
<Compile Include="NegativeTesting.cs" />
<Compile Include="ConsoleEncoding.cs" />
<Compile Include="ConsoleEncoding.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="SyncTextReader.cs" />
<Compile Include="SyncTextWriter.cs" />
<Compile Include="Timeout.cs" />
Expand All @@ -51,11 +49,24 @@
</Compile>
<Compile Include="WindowAndCursorProps.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'" >
<Compile Include="ConsoleEncoding.Windows.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' != 'true'" >
<Compile Include="CancelKeyPress.Unix.cs" />
<Compile Include="NonStandardConfiguration.Unix.cs" />
<Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs">
<Link>Common\Interop\Windows\Interop.Libraries.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Fcntl.cs">
<Link>Interop\Unix\System.Native\Interop.Fcntl.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)\System\Diagnostics\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj">
<Project>{69e46a6f-9966-45a5-8945-2559fe337827}</Project>
<Name>RemoteExecutorConsoleApp</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>

0 comments on commit 6febde9

Please sign in to comment.