diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs
index e27661ea29eff..a12dcc6c09eab 100644
--- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs
+++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs
@@ -18,8 +18,9 @@ namespace System.Net.WebSockets
{
internal sealed class WebSocketHandle
{
- /// Shared, lazily-initialized handler for when using default options.
- private static SocketsHttpHandler? s_defaultHandler;
+ // Shared, lazily-initialized invokers used to avoid some allocations when using default options.
+ private static HttpMessageInvoker? s_defaultInvokerDefaultProxy;
+ private static HttpMessageInvoker? s_defaultInvokerNoProxy;
private readonly CancellationTokenSource _abortSource = new CancellationTokenSource();
private WebSocketState _state = WebSocketState.Connecting;
@@ -47,7 +48,7 @@ public void Abort()
public async Task ConnectAsync(Uri uri, HttpMessageInvoker? invoker, CancellationToken cancellationToken, ClientWebSocketOptions options)
{
- bool disposeHandler = false;
+ bool disposeInvoker = false;
if (invoker is null)
{
if (options.HttpVersion.Major >= 2 || options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrHigher)
@@ -55,7 +56,7 @@ public async Task ConnectAsync(Uri uri, HttpMessageInvoker? invoker, Cancellatio
throw new ArgumentException(SR.net_WebSockets_CustomInvokerRequiredForHttp2, nameof(options));
}
- invoker = new HttpMessageInvoker(SetupHandler(options, out disposeHandler));
+ invoker = SetupInvoker(options, out disposeInvoker);
}
else if (!options.AreCompatibleWithCustomInvoker())
{
@@ -235,44 +236,47 @@ public async Task ConnectAsync(Uri uri, HttpMessageInvoker? invoker, Cancellatio
}
}
- // Disposing the handler will not affect any active stream wrapped in the WebSocket.
- if (disposeHandler)
+ // Disposing the invoker will not affect any active stream wrapped in the WebSocket.
+ if (disposeInvoker)
{
invoker?.Dispose();
}
}
}
- private static SocketsHttpHandler SetupHandler(ClientWebSocketOptions options, out bool disposeHandler)
+ private static HttpMessageInvoker SetupInvoker(ClientWebSocketOptions options, out bool disposeInvoker)
{
- SocketsHttpHandler? handler;
-
- // Create the handler for this request and populate it with all of the options.
- // Try to use a shared handler rather than creating a new one just for this request, if
- // the options are compatible.
- if (options.AreCompatibleWithCustomInvoker() && options.Proxy is null)
+ // Create the invoker for this request and populate it with all of the options.
+ // If the options are compatible, reuse a shared invoker.
+ if (options.AreCompatibleWithCustomInvoker())
{
- disposeHandler = false;
- handler = s_defaultHandler;
- if (handler == null)
+ disposeInvoker = false;
+
+ bool useDefaultProxy = options.Proxy is not null;
+
+ ref HttpMessageInvoker? invokerRef = ref useDefaultProxy ? ref s_defaultInvokerDefaultProxy : ref s_defaultInvokerNoProxy;
+
+ if (invokerRef is null)
{
- handler = new SocketsHttpHandler()
+ var invoker = new HttpMessageInvoker(new SocketsHttpHandler()
{
PooledConnectionLifetime = TimeSpan.Zero,
- UseProxy = false,
+ UseProxy = useDefaultProxy,
UseCookies = false,
- };
- if (Interlocked.CompareExchange(ref s_defaultHandler, handler, null) != null)
+ });
+
+ if (Interlocked.CompareExchange(ref invokerRef, invoker, null) is not null)
{
- handler.Dispose();
- handler = s_defaultHandler;
+ invoker.Dispose();
}
}
+
+ return invokerRef;
}
else
{
- disposeHandler = true;
- handler = new SocketsHttpHandler();
+ disposeInvoker = true;
+ var handler = new SocketsHttpHandler();
handler.PooledConnectionLifetime = TimeSpan.Zero;
handler.CookieContainer = options.Cookies;
handler.UseCookies = options.Cookies != null;
@@ -297,9 +301,9 @@ private static SocketsHttpHandler SetupHandler(ClientWebSocketOptions options, o
handler.SslOptions.ClientCertificates = new X509Certificate2Collection();
handler.SslOptions.ClientCertificates.AddRange(options.ClientCertificates);
}
- }
- return handler;
+ return new HttpMessageInvoker(handler);
+ }
}
private static WebSocketDeflateOptions ParseDeflateOptions(ReadOnlySpan extension, WebSocketDeflateOptions original)
diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs
index 257436bbb77fa..b2137a7faa7a2 100644
--- a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs
+++ b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketOptionsTests.cs
@@ -48,7 +48,6 @@ public static void Proxy_Roundtrips()
[OuterLoop]
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/43751")]
public async Task Proxy_SetNull_ConnectsSuccessfully(Uri server)
{
for (int i = 0; i < 3; i++) // Connect and disconnect multiple times to exercise shared handler on netcoreapp