Skip to content

Commit

Permalink
Handle EAGAIN in Console.Write (dotnet/corefx#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.

Commit migrated from dotnet/corefx@e84604e
  • Loading branch information
stephentoub authored Aug 30, 2017
1 parent bb6abf5 commit b0b7da4
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/libraries/System.Console/src/System.Console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,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/libraries/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
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/libraries/System.Console/tests/System.Console.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Release|AnyCPU'" />
<ItemGroup>
<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 @@ -22,7 +21,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 @@ -39,11 +37,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 b0b7da4

Please sign in to comment.