< Summary - Jellyfin

Information
Class: Jellyfin.Networking.HappyEyeballs.HttpClientExtension
Assembly: Jellyfin.Networking
File(s): /srv/git/jellyfin/src/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs
Line coverage
38%
Covered lines: 12
Uncovered lines: 19
Coverable lines: 31
Total lines: 119
Line coverage: 38.7%
Branch coverage
14%
Covered branches: 2
Total branches: 14
Branch coverage: 14.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 100% (1/1) Total lines: 1194/19/2026 - 12:14:27 AM Line coverage: 38.7% (12/31) Branch coverage: 14.2% (2/14) Total lines: 119 4/19/2026 - 12:14:27 AM Line coverage: 38.7% (12/31) Branch coverage: 14.2% (2/14) Total lines: 119

Coverage delta

Coverage delta 62 -62

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
OnConnect()14.28%1311415.78%
AttemptConnection()100%1172.72%

File(s)

/srv/git/jellyfin/src/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs

#LineLine coverage
 1/*
 2The MIT License (MIT)
 3
 4Copyright (c) .NET Foundation and Contributors
 5
 6All rights reserved.
 7
 8Permission is hereby granted, free of charge, to any person obtaining a copy
 9of this software and associated documentation files (the "Software"), to deal
 10in the Software without restriction, including without limitation the rights
 11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12copies of the Software, and to permit persons to whom the Software is
 13furnished to do so, subject to the following conditions:
 14
 15The above copyright notice and this permission notice shall be included in all
 16copies or substantial portions of the Software.
 17
 18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 24SOFTWARE.
 25*/
 26
 27using System.IO;
 28using System.Net.Http;
 29using System.Net.Sockets;
 30using System.Threading;
 31using System.Threading.Tasks;
 32
 33namespace Jellyfin.Networking.HappyEyeballs;
 34
 35/// <summary>
 36/// Defines the <see cref="HttpClientExtension"/> class.
 37///
 38/// Implementation taken from https://github.com/ppy/osu-framework/pull/4191 .
 39/// </summary>
 40public static class HttpClientExtension
 41{
 42    /// <summary>
 43    /// Gets or sets a value indicating whether the client should use IPv6.
 44    /// </summary>
 345    public static bool UseIPv6 { get; set; } = true;
 46
 47    /// <summary>
 48    /// Implements the httpclient callback method.
 49    /// </summary>
 50    /// <param name="context">The <see cref="SocketsHttpConnectionContext"/> instance.</param>
 51    /// <param name="cancellationToken">The <see cref="CancellationToken"/> instance.</param>
 52    /// <returns>The http steam.</returns>
 53    public static async ValueTask<Stream> OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellation
 54    {
 455        if (!UseIPv6)
 56        {
 457            return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false)
 58        }
 59
 060        using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 061        var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token);
 62
 63        // GetAwaiter().GetResult() is used instead of .Result as this results in improved exception handling.
 64        // The tasks have already been completed.
 65        // See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details.
 066        if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConne
 67        {
 068            await cancelIPv6.CancelAsync().ConfigureAwait(false);
 069            return tryConnectAsyncIPv6.GetAwaiter().GetResult();
 70        }
 71
 072        using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 073        var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token);
 74
 075        if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6)
 76        {
 077            if (tryConnectAsyncIPv6.IsCompletedSuccessfully)
 78            {
 079                await cancelIPv4.CancelAsync().ConfigureAwait(false);
 080                return tryConnectAsyncIPv6.GetAwaiter().GetResult();
 81            }
 82
 083            return tryConnectAsyncIPv4.GetAwaiter().GetResult();
 84        }
 85        else
 86        {
 087            if (tryConnectAsyncIPv4.IsCompletedSuccessfully)
 88            {
 089                await cancelIPv6.CancelAsync().ConfigureAwait(false);
 090                return tryConnectAsyncIPv4.GetAwaiter().GetResult();
 91            }
 92
 093            return tryConnectAsyncIPv6.GetAwaiter().GetResult();
 94        }
 495    }
 96
 97    private static async Task<Stream> AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext contex
 98    {
 99        // The following socket constructor will create a dual-mode socket on systems where IPV6 is available.
 4100        var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)
 4101        {
 4102            // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
 4103            NoDelay = true
 4104        };
 105
 106        try
 107        {
 4108            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
 109            // The stream should take the ownership of the underlying socket,
 110            // closing it when it's disposed.
 4111            return new NetworkStream(socket, ownsSocket: true);
 112        }
 0113        catch
 114        {
 0115            socket.Dispose();
 0116            throw;
 117        }
 4118    }
 119}