22// The .NET Foundation licenses this file to you under the MIT license.
33// See the LICENSE file in the project root for more information.
44
5+ using System . Collections . Concurrent ;
56using System . Diagnostics ;
67using System . IO ;
78using System . Net . Security ;
@@ -15,6 +16,11 @@ namespace System.Net.Http
1516{
1617 internal static class ConnectHelper
1718 {
19+ /// <summary>Pool of event args to use to establish connections.</summary>
20+ private static readonly ConcurrentQueue < ConnectEventArgs > . Segment s_connectEventArgs =
21+ new ConcurrentQueue < ConnectEventArgs > . Segment (
22+ ConcurrentQueue < ConnectEventArgs > . Segment . RoundUpToPowerOf2 ( Environment . ProcessorCount ) ) ;
23+
1824 /// <summary>
1925 /// Helper type used by HttpClientHandler when wrapping SocketsHttpHandler to map its
2026 /// certificate validation callback to the one used by SslStream.
@@ -34,86 +40,100 @@ public CertificateCallbackMapper(Func<HttpRequestMessage, X509Certificate2, X509
3440
3541 public static async ValueTask < ( Socket , Stream ) > ConnectAsync ( string host , int port , CancellationToken cancellationToken )
3642 {
37- try
43+ // Rather than creating a new Socket and calling ConnectAsync on it, we use the static
44+ // Socket.ConnectAsync with a SocketAsyncEventArgs, as we can then use Socket.CancelConnectAsync
45+ // to cancel it if needed. Rent or allocate one.
46+ ConnectEventArgs saea ;
47+ if ( ! s_connectEventArgs . TryDequeue ( out saea ) )
3848 {
39- // Rather than creating a new Socket and calling ConnectAsync on it, we use the static
40- // Socket.ConnectAsync with a SocketAsyncEventArgs, as we can then use Socket.CancelConnectAsync
41- // to cancel it if needed.
42- using ( var saea = new BuilderAndCancellationTokenSocketAsyncEventArgs ( cancellationToken ) )
43- {
44- // Configure which server to which to connect.
45- saea . RemoteEndPoint = IPAddress . TryParse ( host , out IPAddress address ) ?
46- ( EndPoint ) new IPEndPoint ( address , port ) :
47- new DnsEndPoint ( host , port ) ;
49+ saea = new ConnectEventArgs ( ) ;
50+ }
51+ saea . Initialize ( cancellationToken ) ;
4852
49- // Hook up a callback that'll complete the Task when the operation completes.
50- saea . Completed += ( s , e ) =>
51- {
52- var csaea = ( BuilderAndCancellationTokenSocketAsyncEventArgs ) e ;
53- switch ( e . SocketError )
54- {
55- case SocketError . Success :
56- csaea . Builder . SetResult ( ) ;
57- break ;
58- case SocketError . OperationAborted :
59- case SocketError . ConnectionAborted :
60- if ( csaea . CancellationToken . IsCancellationRequested )
61- {
62- csaea . Builder . SetException ( CancellationHelper . CreateOperationCanceledException ( null , csaea . CancellationToken ) ) ;
63- break ;
64- }
65- goto default ;
66- default :
67- csaea . Builder . SetException ( new SocketException ( ( int ) e . SocketError ) ) ;
68- break ;
69- }
70- } ;
53+ try
54+ {
55+ // Configure which server to which to connect.
56+ saea . RemoteEndPoint = IPAddress . TryParse ( host , out IPAddress address ) ?
57+ ( EndPoint ) new IPEndPoint ( address , port ) :
58+ new DnsEndPoint ( host , port ) ;
7159
72- // Initiate the connection.
73- if ( Socket . ConnectAsync ( SocketType . Stream , ProtocolType . Tcp , saea ) )
74- {
75- // Connect completing asynchronously. Enable it to be canceled and wait for it.
76- using ( cancellationToken . Register ( s => Socket . CancelConnectAsync ( ( SocketAsyncEventArgs ) s ) , saea ) )
77- {
78- await saea . Builder . Task . ConfigureAwait ( false ) ;
79- }
80- }
81- else if ( saea . SocketError != SocketError . Success )
60+ // Initiate the connection.
61+ if ( Socket . ConnectAsync ( SocketType . Stream , ProtocolType . Tcp , saea ) )
62+ {
63+ // Connect completing asynchronously. Enable it to be canceled and wait for it.
64+ using ( cancellationToken . Register ( s => Socket . CancelConnectAsync ( ( SocketAsyncEventArgs ) s ) , saea ) )
8265 {
83- // Connect completed synchronously but unsuccessfully.
84- throw new SocketException ( ( int ) saea . SocketError ) ;
66+ await saea . Builder . Task . ConfigureAwait ( false ) ;
8567 }
86-
87- Debug . Assert ( saea . SocketError == SocketError . Success , $ "Expected Success, got { saea . SocketError } .") ;
88- Debug . Assert ( saea . ConnectSocket != null , "Expected non-null socket" ) ;
89-
90- // Configure the socket and return a stream for it.
91- Socket socket = saea . ConnectSocket ;
92- socket . NoDelay = true ;
93- return ( socket , new NetworkStream ( socket , ownsSocket : true ) ) ;
9468 }
69+ else if ( saea . SocketError != SocketError . Success )
70+ {
71+ // Connect completed synchronously but unsuccessfully.
72+ throw new SocketException ( ( int ) saea . SocketError ) ;
73+ }
74+
75+ Debug . Assert ( saea . SocketError == SocketError . Success , $ "Expected Success, got { saea . SocketError } .") ;
76+ Debug . Assert ( saea . ConnectSocket != null , "Expected non-null socket" ) ;
77+
78+ // Configure the socket and return a stream for it.
79+ Socket socket = saea . ConnectSocket ;
80+ socket . NoDelay = true ;
81+ return ( socket , new NetworkStream ( socket , ownsSocket : true ) ) ;
9582 }
9683 catch ( Exception error )
9784 {
9885 throw CancellationHelper . ShouldWrapInOperationCanceledException ( error , cancellationToken ) ?
9986 CancellationHelper . CreateOperationCanceledException ( error , cancellationToken ) :
10087 new HttpRequestException ( error . Message , error ) ;
10188 }
89+ finally
90+ {
91+ // Pool the event args, or if the pool is full, dispose of it.
92+ saea . Clear ( ) ;
93+ if ( ! s_connectEventArgs . TryEnqueue ( saea ) )
94+ {
95+ saea . Dispose ( ) ;
96+ }
97+ }
10298 }
10399
104100 /// <summary>SocketAsyncEventArgs that carries with it additional state for a Task builder and a CancellationToken.</summary>
105- private sealed class BuilderAndCancellationTokenSocketAsyncEventArgs : SocketAsyncEventArgs
101+ private sealed class ConnectEventArgs : SocketAsyncEventArgs
106102 {
107- public AsyncTaskMethodBuilder Builder { get ; }
108- public CancellationToken CancellationToken { get ; }
103+ public AsyncTaskMethodBuilder Builder { get ; private set ; }
104+ public CancellationToken CancellationToken { get ; private set ; }
109105
110- public BuilderAndCancellationTokenSocketAsyncEventArgs ( CancellationToken cancellationToken )
106+ public void Initialize ( CancellationToken cancellationToken )
111107 {
108+ CancellationToken = cancellationToken ;
112109 var b = new AsyncTaskMethodBuilder ( ) ;
113110 var ignored = b . Task ; // force initialization
114111 Builder = b ;
112+ }
115113
116- CancellationToken = cancellationToken ;
114+ public void Clear ( ) => CancellationToken = default ;
115+
116+ protected override void OnCompleted ( SocketAsyncEventArgs _ )
117+ {
118+ switch ( SocketError )
119+ {
120+ case SocketError . Success :
121+ Builder . SetResult ( ) ;
122+ break ;
123+
124+ case SocketError . OperationAborted :
125+ case SocketError . ConnectionAborted :
126+ if ( CancellationToken . IsCancellationRequested )
127+ {
128+ Builder . SetException ( CancellationHelper . CreateOperationCanceledException ( null , CancellationToken ) ) ;
129+ break ;
130+ }
131+ goto default ;
132+
133+ default :
134+ Builder . SetException ( new SocketException ( ( int ) SocketError ) ) ;
135+ break ;
136+ }
117137 }
118138 }
119139
0 commit comments