1- using System ;
1+ using BenchmarkDotNet . Extensions ;
2+ using System ;
23using System . Buffers ;
34using System . Buffers . Binary ;
45using System . Diagnostics ;
@@ -14,23 +15,28 @@ namespace BenchmarkDotNet.Helpers;
1415internal 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 ;
0 commit comments