Skip to content

Commit 6d69c28

Browse files
committed
Use synchronous APIs on Full Framework + Windows Arm.
1 parent 7f2cd06 commit 6d69c28

3 files changed

Lines changed: 43 additions & 7 deletions

File tree

src/BenchmarkDotNet/Helpers/CancelableStreamReader.cs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using BenchmarkDotNet.Extensions;
2+
using System;
23
using System.Buffers;
34
using System.Buffers.Binary;
45
using System.Diagnostics;
@@ -14,23 +15,28 @@ namespace BenchmarkDotNet.Helpers;
1415
internal sealed class CancelableStreamReader : IDisposable
1516
{
1617
#if NET7_0_OR_GREATER
17-
private readonly StreamReader _reader;
18+
private readonly StreamReader _defaultReader;
1819

1920
public CancelableStreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize = -1, bool leaveOpen = false)
20-
=> _reader = new(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen);
21+
=> _defaultReader = new(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen);
2122

2223
public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
23-
=> _reader.ReadLineAsync(cancellationToken);
24+
=> _defaultReader.ReadLineAsync(cancellationToken);
2425

2526
public void Dispose()
26-
=> _reader.Dispose();
27+
=> _defaultReader.Dispose();
2728
#else
2829
// Impl copied from https://github.com/dotnet/runtime/blob/407f9f1476709c3e5aea25511b330e5c1df13fb8/src/libraries/System.Private.CoreLib/src/System/IO/StreamReader.cs
2930
// slightly adjusted to work in netstandard2.0.
3031

3132
private const int DefaultBufferSize = 1024;
3233
private const int MinBufferSize = 128;
3334

35+
// Full Framework on Windows Arm runs on a compatibility layer rather than native.
36+
// This difference was observed to cause hangs with async stream APIs over TcpClient that doesn't happen on other native supported environments.
37+
// In that environment we change the async read to the default sync read via Task.Run.
38+
private readonly StreamReader _defaultReader;
39+
3440
private readonly Stream _stream;
3541
private Encoding _encoding;
3642
private Decoder _decoder;
@@ -51,6 +57,18 @@ public CancelableStreamReader(Stream stream, Encoding encoding, bool detectEncod
5157
if (stream == null) throw new ArgumentNullException(nameof(stream));
5258
if (!stream.CanRead) throw new ArgumentException("Stream is not readable", nameof(stream));
5359

60+
if (Portability.RuntimeInformation.IsFullFrameworkCompatibilityLayer)
61+
{
62+
_defaultReader = new(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize < 0 ? DefaultBufferSize : bufferSize, leaveOpen);
63+
_closable = true;
64+
_stream = null!;
65+
_encoding = null!;
66+
_decoder = null!;
67+
_byteBuffer = null!;
68+
_charBuffer = null!;
69+
return;
70+
}
71+
5472
if (bufferSize == -1)
5573
{
5674
bufferSize = DefaultBufferSize;
@@ -74,6 +92,7 @@ public CancelableStreamReader(Stream stream, Encoding encoding, bool detectEncod
7492
_checkPreamble = preambleLength > 0 && preambleLength <= bufferSize;
7593

7694
_closable = !leaveOpen;
95+
_defaultReader = null!;
7796
}
7897

7998
~CancelableStreamReader()
@@ -99,7 +118,14 @@ private void Dispose(bool disposing)
99118
{
100119
if (disposing)
101120
{
102-
_stream.Close();
121+
if (Portability.RuntimeInformation.IsFullFrameworkCompatibilityLayer)
122+
{
123+
_defaultReader.Dispose();
124+
}
125+
else
126+
{
127+
_stream.Close();
128+
}
103129
}
104130
}
105131
finally
@@ -112,6 +138,12 @@ private void Dispose(bool disposing)
112138

113139
public async ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
114140
{
141+
if (Portability.RuntimeInformation.IsFullFrameworkCompatibilityLayer)
142+
{
143+
cancellationToken.ThrowIfCancellationRequested();
144+
return await Task.Run(() => _defaultReader.ReadLine(), cancellationToken).WaitAsync(cancellationToken);
145+
}
146+
115147
if (_charPos == _charLen && (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0)
116148
{
117149
return null;

src/BenchmarkDotNet/Loggers/Broker.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ private async ValueTask<Result> ProcessDataCore()
9494
}
9595
using var _ = client;
9696
#else
97-
using var client = await tcpListener.AcceptTcpClientAsync().WaitAsync(TcpHost.ConnectionTimeout, cancellationTokenSource.Token);
97+
using var client = Portability.RuntimeInformation.IsFullFrameworkCompatibilityLayer
98+
? await Task.Run(() => tcpListener.AcceptTcpClient(), cancellationTokenSource.Token).WaitAsync(TcpHost.ConnectionTimeout, cancellationTokenSource.Token)
99+
: await tcpListener.AcceptTcpClientAsync().WaitAsync(TcpHost.ConnectionTimeout, cancellationTokenSource.Token);
98100
#endif
99101
using var stream = client.GetStream();
100102
using CancelableStreamReader reader = new(stream, TcpHost.UTF8NoBOM, detectEncodingFromByteOrderMarks: false);

src/BenchmarkDotNet/Portability/RuntimeInformation.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ internal static class RuntimeInformation
4545
FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase);
4646
#endif
4747

48+
public static readonly bool IsFullFrameworkCompatibilityLayer = IsFullFramework && GetCurrentPlatform() is not (Platform.X86 or Platform.X64);
49+
4850
[SupportedOSPlatformGuard("browser")]
4951
#if NET6_0_OR_GREATER
5052
public static readonly bool IsWasm = OperatingSystem.IsBrowser();

0 commit comments

Comments
 (0)