Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Avoid allocations in ClientWebSocket.ConnectAsync in the common case
  • Loading branch information
MihaZupan committed Sep 2, 2022
commit c43a80f5440071583a1c2a9c8c7be137132fd8db
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ namespace System.Net.WebSockets
{
internal sealed class WebSocketHandle
{
/// <summary>Shared, lazily-initialized handler for when using default options.</summary>
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;
Expand Down Expand Up @@ -47,15 +48,15 @@ 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)
{
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())
{
Expand Down Expand Up @@ -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 compabible, try to 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;
Expand All @@ -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<char> extension, WebSocketDeflateOptions original)
Expand Down