Skip to content

Commit 1e478eb

Browse files
committed
Fix polling on https connections in HttpConnectionPool
Avoid performing read-aheads on such connections, which can result in long-pinned buffers and unnecessary faulted tasks when the connections are torn down.
1 parent b6f0c7e commit 1e478eb

File tree

2 files changed

+33
-19
lines changed

2 files changed

+33
-19
lines changed

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ internal partial class HttpConnection : HttpConnectionBase, IDisposable
7171

7272
public HttpConnection(
7373
HttpConnectionPool pool,
74+
Socket? socket,
7475
Stream stream,
7576
TransportContext? transportContext)
7677
{
@@ -79,10 +80,7 @@ public HttpConnection(
7980

8081
_pool = pool;
8182
_stream = stream;
82-
if (stream is NetworkStream networkStream)
83-
{
84-
_socket = networkStream.Socket;
85-
}
83+
_socket = socket;
8684

8785
_transportContext = transportContext;
8886

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ private static bool IsUsableHttp11Connection(HttpConnection connection, long now
567567
}
568568

569569
// Try to establish an HTTP2 connection
570+
Socket? socket = null;
570571
Stream? stream = null;
571572
SslStream? sslStream = null;
572573
TransportContext? transportContext = null;
@@ -591,7 +592,7 @@ private static bool IsUsableHttp11Connection(HttpConnection connection, long now
591592

592593
HttpResponseMessage? failureResponse;
593594

594-
(stream, transportContext, failureResponse) =
595+
(socket, stream, transportContext, failureResponse) =
595596
await ConnectAsync(request, async, cancellationToken).ConfigureAwait(false);
596597

597598
if (failureResponse != null)
@@ -681,7 +682,7 @@ private static bool IsUsableHttp11Connection(HttpConnection connection, long now
681682

682683
if (canUse)
683684
{
684-
return (await ConstructHttp11ConnectionAsync(async, stream!, transportContext, request, cancellationToken).ConfigureAwait(false), true, null);
685+
return (await ConstructHttp11ConnectionAsync(async, socket, stream!, transportContext, request, cancellationToken).ConfigureAwait(false), true, null);
685686
}
686687
else
687688
{
@@ -1213,7 +1214,7 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
12131214
return SendWithProxyAuthAsync(request, async, doRequestAuth, cancellationToken);
12141215
}
12151216

1216-
private async ValueTask<(Stream?, TransportContext?, HttpResponseMessage?)> ConnectAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
1217+
private async ValueTask<(Socket?, Stream?, TransportContext?, HttpResponseMessage?)> ConnectAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
12171218
{
12181219
// If a non-infinite connect timeout has been set, create and use a new CancellationToken that will be canceled
12191220
// when either the original token is canceled or a connect timeout occurs.
@@ -1228,17 +1229,18 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
12281229
try
12291230
{
12301231
Stream? stream = null;
1232+
Socket? socket = null;
12311233
switch (_kind)
12321234
{
12331235
case HttpConnectionKind.Http:
12341236
case HttpConnectionKind.Https:
12351237
case HttpConnectionKind.ProxyConnect:
12361238
Debug.Assert(_originAuthority != null);
1237-
stream = await ConnectToTcpHostAsync(_originAuthority.IdnHost, _originAuthority.Port, request, async, cancellationToken).ConfigureAwait(false);
1239+
(socket, stream) = await ConnectToTcpHostAsync(_originAuthority.IdnHost, _originAuthority.Port, request, async, cancellationToken).ConfigureAwait(false);
12381240
break;
12391241

12401242
case HttpConnectionKind.Proxy:
1241-
stream = await ConnectToTcpHostAsync(_proxyUri!.IdnHost, _proxyUri.Port, request, async, cancellationToken).ConfigureAwait(false);
1243+
(socket, stream) = await ConnectToTcpHostAsync(_proxyUri!.IdnHost, _proxyUri.Port, request, async, cancellationToken).ConfigureAwait(false);
12421244
break;
12431245

12441246
case HttpConnectionKind.ProxyTunnel:
@@ -1249,12 +1251,18 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
12491251
{
12501252
// Return non-success response from proxy.
12511253
response.RequestMessage = request;
1252-
return (null, null, response);
1254+
return (null, null, null, response);
12531255
}
12541256
break;
12551257
}
12561258

12571259
Debug.Assert(stream != null);
1260+
if (socket is null && stream is NetworkStream ns)
1261+
{
1262+
// We weren't handed a socket directly. But if we're able to extract one, do so.
1263+
// Most likely case here is a ConnectCallback was used and returned a NetworkStream.
1264+
socket = ns.Socket;
1265+
}
12581266

12591267
TransportContext? transportContext = null;
12601268
if (IsSecure)
@@ -1264,20 +1272,21 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
12641272
stream = sslStream;
12651273
}
12661274

1267-
return (stream, transportContext, null);
1275+
return (socket, stream, transportContext, null);
12681276
}
12691277
finally
12701278
{
12711279
cancellationWithConnectTimeout?.Dispose();
12721280
}
12731281
}
12741282

1275-
private async ValueTask<Stream> ConnectToTcpHostAsync(string host, int port, HttpRequestMessage initialRequest, bool async, CancellationToken cancellationToken)
1283+
private async ValueTask<(Socket?, Stream)> ConnectToTcpHostAsync(string host, int port, HttpRequestMessage initialRequest, bool async, CancellationToken cancellationToken)
12761284
{
12771285
cancellationToken.ThrowIfCancellationRequested();
12781286

12791287
var endPoint = new DnsEndPoint(host, port);
12801288
Socket? socket = null;
1289+
Stream? stream = null;
12811290
try
12821291
{
12831292
// If a ConnectCallback was supplied, use that to establish the connection.
@@ -1294,7 +1303,7 @@ private async ValueTask<Stream> ConnectToTcpHostAsync(string host, int port, Htt
12941303
Trace($"{nameof(SocketsHttpHandler.ConnectCallback)} completing asynchronously for a synchronous request.");
12951304
}
12961305

1297-
return await streamTask.ConfigureAwait(false) ?? throw new HttpRequestException(SR.net_http_null_from_connect_callback);
1306+
stream = await streamTask.ConfigureAwait(false) ?? throw new HttpRequestException(SR.net_http_null_from_connect_callback);
12981307
}
12991308
else
13001309
{
@@ -1313,8 +1322,10 @@ private async ValueTask<Stream> ConnectToTcpHostAsync(string host, int port, Htt
13131322
}
13141323
}
13151324

1316-
return new NetworkStream(socket, ownsSocket: true);
1325+
stream = new NetworkStream(socket, ownsSocket: true);
13171326
}
1327+
1328+
return (socket, stream);
13181329
}
13191330
catch (Exception ex)
13201331
{
@@ -1327,15 +1338,15 @@ private async ValueTask<Stream> ConnectToTcpHostAsync(string host, int port, Htt
13271338

13281339
internal async ValueTask<(HttpConnection?, HttpResponseMessage?)> CreateHttp11ConnectionAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
13291340
{
1330-
(Stream? stream, TransportContext? transportContext, HttpResponseMessage? failureResponse) =
1341+
(Socket? socket, Stream? stream, TransportContext? transportContext, HttpResponseMessage? failureResponse) =
13311342
await ConnectAsync(request, async, cancellationToken).ConfigureAwait(false);
13321343

13331344
if (failureResponse != null)
13341345
{
13351346
return (null, failureResponse);
13361347
}
13371348

1338-
return (await ConstructHttp11ConnectionAsync(async, stream!, transportContext, request, cancellationToken).ConfigureAwait(false), null);
1349+
return (await ConstructHttp11ConnectionAsync(async, socket, stream!, transportContext, request, cancellationToken).ConfigureAwait(false), null);
13391350
}
13401351

13411352
private SslClientAuthenticationOptions GetSslOptionsForRequest(HttpRequestMessage request)
@@ -1393,10 +1404,15 @@ private async ValueTask<Stream> ApplyPlaintextFilterAsync(bool async, Stream str
13931404
return newStream;
13941405
}
13951406

1396-
private async ValueTask<HttpConnection> ConstructHttp11ConnectionAsync(bool async, Stream stream, TransportContext? transportContext, HttpRequestMessage request, CancellationToken cancellationToken)
1407+
private async ValueTask<HttpConnection> ConstructHttp11ConnectionAsync(bool async, Socket? socket, Stream stream, TransportContext? transportContext, HttpRequestMessage request, CancellationToken cancellationToken)
13971408
{
1398-
stream = await ApplyPlaintextFilterAsync(async, stream, HttpVersion.Version11, request, cancellationToken).ConfigureAwait(false);
1399-
return new HttpConnection(this, stream, transportContext);
1409+
Stream newStream = await ApplyPlaintextFilterAsync(async, stream, HttpVersion.Version11, request, cancellationToken).ConfigureAwait(false);
1410+
if (newStream != stream)
1411+
{
1412+
// If a plaintext filter created a new stream, we can't trust that the socket is still applicable, so rely on it.
1413+
socket = null;
1414+
}
1415+
return new HttpConnection(this, socket, newStream, transportContext);
14001416
}
14011417

14021418
private async ValueTask<Http2Connection> ConstructHttp2ConnectionAsync(Stream stream, HttpRequestMessage request, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)