Skip to content

Commit 9aa8cbf

Browse files
authored
Avoid capturing the ExecutionContext for the whole HTTP connection lifetime (#111475)
1 parent f2363c7 commit 9aa8cbf

File tree

4 files changed

+50
-12
lines changed

4 files changed

+50
-12
lines changed

src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Asynchrony.cs

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public async Task ResponseHeadersRead_SynchronizationContextNotUsedByHandler(boo
3131
return;
3232
}
3333

34+
if (UseVersion.Major != 1)
35+
{
36+
return;
37+
}
38+
3439
await Task.Run(async delegate // escape xunit's sync ctx
3540
{
3641
await LoopbackServer.CreateClientAndServerAsync(uri =>

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,12 @@ public async ValueTask SetupAsync(CancellationToken cancellationToken)
225225

226226
// Processing the incoming frames before sending the client preface and SETTINGS is necessary when using a NamedPipe as a transport.
227227
// If the preface and SETTINGS coming from the server are not read first the below WriteAsync and the ProcessIncomingFramesAsync fall into a deadlock.
228-
_ = ProcessIncomingFramesAsync();
228+
// Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
229+
using (ExecutionContext.SuppressFlow())
230+
{
231+
_ = ProcessIncomingFramesAsync();
232+
}
233+
229234
await _stream.WriteAsync(_outgoingBuffer.ActiveMemory, cancellationToken).ConfigureAwait(false);
230235
_rttEstimator.OnInitialSettingsSent();
231236
_outgoingBuffer.ClearAndReturnBuffer();
@@ -249,7 +254,11 @@ public async ValueTask SetupAsync(CancellationToken cancellationToken)
249254
throw new IOException(SR.net_http_http2_connection_not_established, e);
250255
}
251256

252-
_ = ProcessOutgoingFramesAsync();
257+
// Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
258+
using (ExecutionContext.SuppressFlow())
259+
{
260+
_ = ProcessOutgoingFramesAsync();
261+
}
253262
}
254263

255264
private void Shutdown()

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,15 @@ public void InitQuicConnection(QuicConnection connection, Activity? connectionSe
9595

9696
_connection = connection;
9797

98-
// Errors are observed via Abort().
99-
_sendSettingsTask = SendSettingsAsync();
98+
// Avoid capturing the initial request's ExecutionContext for the entire lifetime of the new connection.
99+
using (ExecutionContext.SuppressFlow())
100+
{
101+
// Errors are observed via Abort().
102+
_sendSettingsTask = SendSettingsAsync();
100103

101-
// This process is cleaned up when _connection is disposed, and errors are observed via Abort().
102-
_ = AcceptStreamsAsync();
104+
// This process is cleaned up when _connection is disposed, and errors are observed via Abort().
105+
_ = AcceptStreamsAsync();
106+
}
103107
}
104108

105109
/// <summary>

src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs

+26-6
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ namespace System.Net.Http.Functional.Tests
2828
{
2929
using Configuration = System.Net.Test.Common.Configuration;
3030

31-
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
32-
public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test : HttpClientHandler_Asynchrony_Test
31+
public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http11 : SocketsHttpHandler_HttpClientHandler_Asynchrony_Test
3332
{
34-
public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { }
33+
public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http11(ITestOutputHelper output) : base(output) { }
3534

3635
[OuterLoop("Relies on finalization")]
3736
[Fact]
@@ -98,6 +97,25 @@ static async Task MakeARequestWithoutDisposingTheHandlerAsync()
9897
requestCompleted.SetResult();
9998
}
10099
}
100+
}
101+
102+
public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http2 : SocketsHttpHandler_HttpClientHandler_Asynchrony_Test
103+
{
104+
public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http2(ITestOutputHelper output) : base(output) { }
105+
protected override Version UseVersion => HttpVersion.Version20;
106+
}
107+
108+
[ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))]
109+
public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http3 : SocketsHttpHandler_HttpClientHandler_Asynchrony_Test
110+
{
111+
public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test_Http3(ITestOutputHelper output) : base(output) { }
112+
protected override Version UseVersion => HttpVersion.Version30;
113+
}
114+
115+
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
116+
public abstract class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test : HttpClientHandler_Asynchrony_Test
117+
{
118+
public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { }
101119

102120
[Fact]
103121
public async Task ReadAheadTaskOnConnectionReuse_ExceptionsAreObserved()
@@ -191,7 +209,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(
191209
public async Task ExecutionContext_HttpConnectionLifetimeDoesntKeepContextAlive()
192210
{
193211
var clientCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
194-
await LoopbackServer.CreateClientAndServerAsync(async uri =>
212+
await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
195213
{
196214
try
197215
{
@@ -217,7 +235,9 @@ await LoopbackServer.CreateClientAndServerAsync(async uri =>
217235
{
218236
await server.AcceptConnectionAsync(async connection =>
219237
{
220-
await connection.ReadRequestHeaderAndSendResponseAsync();
238+
await connection.ReadRequestDataAsync();
239+
await connection.SendResponseAsync();
240+
221241
await clientCompleted.Task;
222242
});
223243
});
@@ -240,7 +260,7 @@ private static (Task completedOnFinalized, Task getRequest) MakeHttpRequestWithT
240260
return (tcs.Task, t);
241261
}
242262

243-
private sealed class SetOnFinalized
263+
protected sealed class SetOnFinalized
244264
{
245265
public readonly TaskCompletionSource CompletedWhenFinalized = new(TaskCreationOptions.RunContinuationsAsynchronously);
246266

0 commit comments

Comments
 (0)