Skip to content

[API Proposal]: ValueTask-based Socket.ConnectAsync overloads for Happy Eyeballs #117548

Description

@antonfirsov

Background and motivation

One of the original motivations behind #861 / #87932 was to utilize Happy Eyeballs in SocketsHttpHandler, see #26177 (comment), however since then the SocketsHttpHandler code has been updated to use the ValueTask overload. Unless we are willing to duplicate code into SocketsHttpHandler, the SAEA overload is insufficient for utilizing Happy Eyeballs in SocketsHttpHandler. Moreover ValueTask-based overloads would be more useful for the general public.

API Proposal

namespace System.Net.Sockets;

public class Socket
{
    // Existing:
    public ValueTask ConnectAsync(EndPoint remoteEP, CancellationToken cancellationToken);
    public static bool ConnectAsync(SocketType socketType, ProtocolType protocolType, SocketAsyncEventArgs e)

    // Approved in #861 / #87932
    public static bool ConnectAsync(SocketType socketType, ProtocolType protocolType, SocketAsyncEventArgs e, ConnectAlgorithm connectAlgorithm);

    // Proposed:
    public ValueTask ConnectAsync(DnsEndPoint remoteEP, ConnectAlgorithm connectAlgorithm, CancellationToken cancellationToken = default);

    // Also consider (needs more implementation work)
    public ValueTask ConnectAsync(IPAddress[] addresses, int port, ConnectAlgorithm connectAlgorithm, CancellationToken cancellationToken)
}

API Usage

Using DnsEndpoint overload in SocketsHttpHandler (simplified)

public async ValueTask<Stream> ConnectToTcpHostAsync(string host, int port, CancellationToken cancellationToken)
{
    try
    {
        Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
        await socket.ConnectAsync(endPoint, ConnectAlgorithm.Parallel, cancellationToken).ConfigureAwait(false);
        return new NetworkStream(socket, ownsSocket: true);
    }
    catch
    {
        socket.Dispose();
        throw;
    }
}

Using IPAddress[] overload in ConnectCallback

using SocketsHttpHandler handler = new SocketsHttpHandler();
handler.ConnectCallback = async (ctx, ct) =>
{
    DnsEndPoint dnsEndPoint = ctx.DnsEndPoint;
    IPAddress[] addresses = await Dns.GetHostAddressesAsync(dnsEndPoint.Host, dnsEndPoint.AddressFamily, ct);

    LogResolvedAdresses(addresses);

    var s = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
    try
    {
        await s.ConnectAsync(addresses, dnsEndPoint.Port, ConnectAlgorithm.Parallel, ct);
        return new NetworkStream(s, ownsSocket: true);
    }
    catch
    {
        s.Dispose();
        throw;
    }
};

Alternative Designs

No response

Risks

I see no high risks from the API itself, however adaption (#26177) should be done with care.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions