Skip to content

Commit 7f614f0

Browse files
quixoticaxisMihaZupan
authored andcommitted
Added an additional yield point prior to possibly long running initialization (#115688)
* Added an additional yield point * Enqueued setup logic to thread pool Also added an explanation comment * Changed validation order to reduce discrepancies between synchronous and asynchronous calls * Updated comment Co-authored-by: Miha Zupan <[email protected]> * Removed Volatile.Read to make the code more readable Co-authored-by: Miha Zupan <[email protected]> * Removed the test that is no longer viable * Moved the nested method to adhere to the code style --------- Co-authored-by: Miha Zupan <[email protected]>
1 parent 22db99b commit 7f614f0

File tree

3 files changed

+20
-83
lines changed

3 files changed

+20
-83
lines changed

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public sealed class SocketsHttpHandler : HttpMessageHandler
2020
{
2121
private readonly HttpConnectionSettings _settings = new HttpConnectionSettings();
2222
private HttpMessageHandlerStage? _handler;
23+
private Task<HttpMessageHandlerStage>? _handlerChainSetupTask;
2324
private Func<HttpConnectionSettings, HttpMessageHandlerStage, HttpMessageHandlerStage>? _decompressionHandlerFactory;
2425
private bool _disposed;
2526

@@ -598,14 +599,14 @@ protected internal override HttpResponseMessage Send(HttpRequestMessage request,
598599

599600
cancellationToken.ThrowIfCancellationRequested();
600601

601-
HttpMessageHandlerStage handler = _handler ?? SetupHandlerChain();
602-
603602
Exception? error = ValidateAndNormalizeRequest(request);
604603
if (error != null)
605604
{
606605
throw error;
607606
}
608607

608+
HttpMessageHandlerStage handler = _handler ?? SetupHandlerChain();
609+
609610
return handler.Send(request, cancellationToken);
610611
}
611612

@@ -620,15 +621,25 @@ protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessa
620621
return Task.FromCanceled<HttpResponseMessage>(cancellationToken);
621622
}
622623

623-
HttpMessageHandlerStage handler = _handler ?? SetupHandlerChain();
624-
625624
Exception? error = ValidateAndNormalizeRequest(request);
626625
if (error != null)
627626
{
628627
return Task.FromException<HttpResponseMessage>(error);
629628
}
630629

631-
return handler.SendAsync(request, cancellationToken);
630+
return _handler is { } handler
631+
? handler.SendAsync(request, cancellationToken)
632+
: CreateHandlerAndSendAsync(request, cancellationToken);
633+
634+
// SetupHandlerChain may block for a few seconds in some environments.
635+
// E.g. during the first access of HttpClient.DefaultProxy - https://github.com/dotnet/runtime/issues/115301.
636+
// The setup procedure is enqueued to thread pool to prevent the caller from blocking.
637+
async Task<HttpResponseMessage> CreateHandlerAndSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
638+
{
639+
_handlerChainSetupTask ??= Task.Run(SetupHandlerChain);
640+
HttpMessageHandlerStage handler = await _handlerChainSetupTask.ConfigureAwait(false);
641+
return await handler.SendAsync(request, cancellationToken).ConfigureAwait(false);
642+
}
632643
}
633644

634645
private static Exception? ValidateAndNormalizeRequest(HttpRequestMessage request)

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

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,83 +1069,6 @@ await RemoteExecutor.Invoke(async (useVersion, testAsync) =>
10691069
}, UseVersion.ToString(), TestAsync.ToString()).DisposeAsync();
10701070
}
10711071

1072-
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
1073-
public async Task SendAsync_ExpectedDiagnosticSynchronousExceptionActivityLogging()
1074-
{
1075-
await RemoteExecutor.Invoke(async (useVersion, testAsync) =>
1076-
{
1077-
Exception exceptionLogged = null;
1078-
1079-
TaskCompletionSource activityStopTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
1080-
1081-
var diagnosticListenerObserver = new FakeDiagnosticListenerObserver(kvp =>
1082-
{
1083-
if (kvp.Key.Equals("System.Net.Http.HttpRequestOut.Stop"))
1084-
{
1085-
Assert.NotNull(kvp.Value);
1086-
GetProperty<HttpRequestMessage>(kvp.Value, "Request");
1087-
TaskStatus requestStatus = GetProperty<TaskStatus>(kvp.Value, "RequestTaskStatus");
1088-
Assert.Equal(TaskStatus.Faulted, requestStatus);
1089-
activityStopTcs.SetResult();
1090-
}
1091-
else if (kvp.Key.Equals("System.Net.Http.Exception"))
1092-
{
1093-
Assert.NotNull(kvp.Value);
1094-
exceptionLogged = GetProperty<Exception>(kvp.Value, "Exception");
1095-
}
1096-
});
1097-
1098-
using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver))
1099-
{
1100-
diagnosticListenerObserver.Enable();
1101-
1102-
using (HttpClientHandler handler = CreateHttpClientHandler(useVersion))
1103-
using (HttpClient client = CreateHttpClient(handler, useVersion))
1104-
{
1105-
// Set a ftp proxy.
1106-
// Forces a synchronous exception for SocketsHttpHandler.
1107-
// SocketsHttpHandler only allow http & https & socks scheme for proxies.
1108-
handler.Proxy = new WebProxy($"ftp://foo.bar", false);
1109-
var request = new HttpRequestMessage(HttpMethod.Get, InvalidUri)
1110-
{
1111-
Version = Version.Parse(useVersion)
1112-
};
1113-
1114-
// We cannot use Assert.Throws<Exception>(() => { SendAsync(...); }) to verify the
1115-
// synchronous exception here, because DiagnosticsHandler SendAsync() method has async
1116-
// modifier, and returns Task. If the call is not awaited, the current test method will continue
1117-
// run before the call is completed, thus Assert.Throws() will not capture the exception.
1118-
// We need to wait for the Task to complete synchronously, to validate the exception.
1119-
1120-
Exception exception = null;
1121-
if (bool.Parse(testAsync))
1122-
{
1123-
Task sendTask = client.SendAsync(request);
1124-
Assert.True(sendTask.IsFaulted);
1125-
exception = sendTask.Exception.InnerException;
1126-
}
1127-
else
1128-
{
1129-
try
1130-
{
1131-
client.Send(request);
1132-
}
1133-
catch (Exception ex)
1134-
{
1135-
exception = ex;
1136-
}
1137-
Assert.NotNull(exception);
1138-
}
1139-
1140-
await activityStopTcs.Task;
1141-
1142-
Assert.IsType<NotSupportedException>(exception);
1143-
Assert.Same(exceptionLogged, exception);
1144-
}
1145-
}
1146-
}, UseVersion.ToString(), TestAsync.ToString()).DisposeAsync();
1147-
}
1148-
11491072
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
11501073
public async Task SendAsync_ExpectedDiagnosticSourceNewAndDeprecatedEventsLogging()
11511074
{

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2595,8 +2595,11 @@ public async Task AfterDisposeSendAsync_GettersUsable_SettersThrow(bool dispose)
25952595
else
25962596
{
25972597
using (var c = new HttpMessageInvoker(handler, disposeHandler: false))
2598+
{
25982599
await Assert.ThrowsAnyAsync<Exception>(() =>
2599-
c.SendAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("/shouldquicklyfail", UriKind.Relative)), default));
2600+
c.SendAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.some.example/shouldquicklyfail", UriKind.Absolute)), default));
2601+
}
2602+
26002603
expectedExceptionType = typeof(InvalidOperationException);
26012604
}
26022605

0 commit comments

Comments
 (0)