From c9a387943f05cbbd11c5b92d900ff850055ed4f3 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:17:56 -0400 Subject: [PATCH 1/8] Add IPv4 fallback from IPv6 failure. Co-authored-by: BaronGreenback --- Emby.Dlna/Eventing/DlnaEventManager.cs | 2 +- .../HappyEyeballs/HttpClientExtension.cs | 117 ++++++++++++++++++ Jellyfin.Networking/Manager/NetworkManager.cs | 17 ++- Jellyfin.Server/Startup.cs | 20 ++- MediaBrowser.Common/Net/NamedClient.cs | 9 +- 5 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index d17e238715..d2c0017ec6 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -165,7 +165,7 @@ namespace Emby.Dlna.Eventing try { - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + using var response = await _httpClientFactory.CreateClient(NamedClient.DirectIp) .SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); } catch (OperationCanceledException) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs new file mode 100644 index 0000000000..12b42cc6e1 --- /dev/null +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -0,0 +1,117 @@ +using System.Diagnostics.Metrics; +using System.IO; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +/* + Copyright (c) 2021 ppy Pty Ltd . + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +namespace Jellyfin.Networking.HappyEyeballs +{ + /// + /// Defines the class. + /// + /// Implementation taken from https://github.com/ppy/osu-framework/pull/4191 . + /// + public static class HttpClientExtension + { + private const int ConnectionEstablishTimeout = 2000; + + /// + /// Gets a value indicating whether the initial IPv6 check has been performed (to determine whether v6 is available or not). + /// + private static bool _hasResolvedIPv6Availability; + + /// + /// Gets or sets a value indicating whether IPv6 should be preferred. Value may change based on runtime failures. + /// + public static bool? UseIPv6 { get; set; } = null; + + /// + /// Implements the httpclient callback method. + /// + /// The instance. + /// The instance. + /// The http steam. + public static async ValueTask OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken) + { + // Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), + // let's make IPv4 fallback work in a simple way. This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 + // and expected to be fixed in .NET 6. + if (UseIPv6 == true) + { + try + { + var localToken = cancellationToken; + + if (!_hasResolvedIPv6Availability) + { + // to make things move fast, use a very low timeout for the initial ipv6 attempt. + using var quickFailCts = new CancellationTokenSource(ConnectionEstablishTimeout); + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token); + + localToken = linkedTokenSource.Token; + } + + return await AttemptConnection(AddressFamily.InterNetworkV6, context, localToken).ConfigureAwait(false); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch +#pragma warning restore CA1031 // Do not catch general exception types + { + // very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt. + // Network manager will reset this value in the case of physical network changes / interruptions. + UseIPv6 = false; + } + finally + { + _hasResolvedIPv6Availability = true; + } + } + + // fallback to IPv4. + return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false); + } + + private static async ValueTask AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) + { + // The following socket constructor will create a dual-mode socket on systems where IPV6 is available. + var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) + { + // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios. + NoDelay = true + }; + + try + { + await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false); + // The stream should take the ownership of the underlying socket, + // closing it when it's disposed. + return new NetworkStream(socket, ownsSocket: true); + } + catch + { + socket.Dispose(); + throw; + } + } + } +} diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 9e06cdfe71..631e9cdb8d 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -594,6 +594,7 @@ namespace Jellyfin.Networking.Manager IsIP4Enabled = Socket.OSSupportsIPv4 && config.EnableIPV4; IsIP6Enabled = Socket.OSSupportsIPv6 && config.EnableIPV6; + HappyEyeballs.HttpClientExtension.UseIPv6 = IsIP6Enabled; if (!IsIP6Enabled && !IsIP4Enabled) { @@ -838,9 +839,19 @@ namespace Jellyfin.Networking.Manager try { await Task.Delay(2000).ConfigureAwait(false); - InitialiseInterfaces(); - // Recalculate LAN caches. - InitialiseLAN(_configurationManager.GetNetworkConfiguration()); + + var config = _configurationManager.GetNetworkConfiguration(); + // Have we lost IPv6 capability? + if (IsIP6Enabled && !Socket.OSSupportsIPv6) + { + UpdateSettings(config); + } + else + { + InitialiseInterfaces(); + // Recalculate LAN caches. + InitialiseLAN(config); + } NetworkChanged?.Invoke(this, EventArgs.Empty); } diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 1954a5c558..4417bb7226 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -7,6 +7,7 @@ using System.Net.Mime; using System.Text; using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.HappyEyeballs; using Jellyfin.Server.Extensions; using Jellyfin.Server.Implementations; using Jellyfin.Server.Infrastructure; @@ -24,6 +25,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; +using Microsoft.VisualBasic; using Prometheus; namespace Jellyfin.Server @@ -79,6 +81,13 @@ namespace Jellyfin.Server var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0); var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9); var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8); + Func eyeballsHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() + { + AutomaticDecompression = DecompressionMethods.All, + RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8, + ConnectCallback = HttpClientExtension.OnConnect + }; + Func defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() { AutomaticDecompression = DecompressionMethods.All, @@ -93,7 +102,7 @@ namespace Jellyfin.Server c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) - .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.MusicBrainz, c => { @@ -102,6 +111,15 @@ namespace Jellyfin.Server c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) + .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); + + services.AddHttpClient(NamedClient.DirectIp, c => + { + c.DefaultRequestHeaders.UserAgent.Add(productHeader); + c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader); + c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); + c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); + }) .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.Dlna, c => diff --git a/MediaBrowser.Common/Net/NamedClient.cs b/MediaBrowser.Common/Net/NamedClient.cs index a6cacd4f17..9c5544b0ff 100644 --- a/MediaBrowser.Common/Net/NamedClient.cs +++ b/MediaBrowser.Common/Net/NamedClient.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Common.Net +namespace MediaBrowser.Common.Net { /// /// Registered http client names. @@ -6,7 +6,7 @@ public static class NamedClient { /// - /// Gets the value for the default named http client. + /// Gets the value for the default named http client which implements happy eyeballs. /// public const string Default = nameof(Default); @@ -19,5 +19,10 @@ /// Gets the value for the DLNA named http client. /// public const string Dlna = nameof(Dlna); + + /// + /// Non happy eyeballs implementation. + /// + public const string DirectIp = nameof(DirectIp); } } From 6c479dfb36d305444bae7d4b3ad78bfb2112549b Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:38:28 -0400 Subject: [PATCH 2/8] Ensure IPv6 property is threadsafe. --- .../HappyEyeballs/HttpClientExtension.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index 12b42cc6e1..6760869107 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -35,6 +35,11 @@ namespace Jellyfin.Networking.HappyEyeballs { private const int ConnectionEstablishTimeout = 2000; + /// + /// Interlocked doesn't support bool types. + /// + private static int _useIPv6 = 0; + /// /// Gets a value indicating whether the initial IPv6 check has been performed (to determine whether v6 is available or not). /// @@ -43,7 +48,21 @@ namespace Jellyfin.Networking.HappyEyeballs /// /// Gets or sets a value indicating whether IPv6 should be preferred. Value may change based on runtime failures. /// - public static bool? UseIPv6 { get; set; } = null; + public static bool UseIPv6 + { + get => Interlocked.CompareExchange(ref _useIPv6, 1, 1) == 1; + set + { + if (value) + { + Interlocked.CompareExchange(ref _useIPv6, 1, 0); + } + else + { + Interlocked.CompareExchange(ref _useIPv6, 0, 1); + } + } + } /// /// Implements the httpclient callback method. @@ -56,7 +75,7 @@ namespace Jellyfin.Networking.HappyEyeballs // Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), // let's make IPv4 fallback work in a simple way. This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 // and expected to be fixed in .NET 6. - if (UseIPv6 == true) + if (UseIPv6) { try { From 6caabe68ff05f4100db63a3a98d6eeb02347f689 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:17:41 -0400 Subject: [PATCH 3/8] Change method to call IPv6 and IPv4 in parallel. --- .../HappyEyeballs/HttpClientExtension.cs | 119 ++++++------------ 1 file changed, 38 insertions(+), 81 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index 6760869107..049c8dd150 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -1,29 +1,9 @@ -using System.Diagnostics.Metrics; using System.IO; using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -/* - Copyright (c) 2021 ppy Pty Ltd . - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - namespace Jellyfin.Networking.HappyEyeballs { /// @@ -33,36 +13,12 @@ namespace Jellyfin.Networking.HappyEyeballs /// public static class HttpClientExtension { - private const int ConnectionEstablishTimeout = 2000; - /// - /// Interlocked doesn't support bool types. + /// Gets or sets a value indicating whether the client should use IPv6. /// - private static int _useIPv6 = 0; + public static bool UseIPv6 { get; set; } = true; - /// - /// Gets a value indicating whether the initial IPv6 check has been performed (to determine whether v6 is available or not). - /// - private static bool _hasResolvedIPv6Availability; - - /// - /// Gets or sets a value indicating whether IPv6 should be preferred. Value may change based on runtime failures. - /// - public static bool UseIPv6 - { - get => Interlocked.CompareExchange(ref _useIPv6, 1, 1) == 1; - set - { - if (value) - { - Interlocked.CompareExchange(ref _useIPv6, 1, 0); - } - else - { - Interlocked.CompareExchange(ref _useIPv6, 0, 1); - } - } - } + // Implementation taken from https://github.com/rmkerr/corefx/blob/SocketsHttpHandler_Connect_HappyEyeballs/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs /// /// Implements the httpclient callback method. @@ -72,45 +28,46 @@ namespace Jellyfin.Networking.HappyEyeballs /// The http steam. public static async ValueTask OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken) { - // Until .NET supports an implementation of Happy Eyeballs (https://tools.ietf.org/html/rfc8305#section-2), - // let's make IPv4 fallback work in a simple way. This issue is being tracked at https://github.com/dotnet/runtime/issues/26177 - // and expected to be fixed in .NET 6. - if (UseIPv6) + if (!UseIPv6) { - try - { - var localToken = cancellationToken; - - if (!_hasResolvedIPv6Availability) - { - // to make things move fast, use a very low timeout for the initial ipv6 attempt. - using var quickFailCts = new CancellationTokenSource(ConnectionEstablishTimeout); - using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, quickFailCts.Token); - - localToken = linkedTokenSource.Token; - } - - return await AttemptConnection(AddressFamily.InterNetworkV6, context, localToken).ConfigureAwait(false); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch -#pragma warning restore CA1031 // Do not catch general exception types - { - // very naively fallback to ipv4 permanently for this execution based on the response of the first connection attempt. - // Network manager will reset this value in the case of physical network changes / interruptions. - UseIPv6 = false; - } - finally - { - _hasResolvedIPv6Availability = true; - } + return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false); } - // fallback to IPv4. - return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false); + using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token); + + if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully) + { + cancelIPv6.Cancel(); + return tryConnectAsyncIPv6.GetAwaiter().GetResult(); + } + + using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token); + + if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6) + { + if (tryConnectAsyncIPv6.IsCompletedSuccessfully) + { + cancelIPv4.Cancel(); + return tryConnectAsyncIPv6.GetAwaiter().GetResult(); + } + + return tryConnectAsyncIPv4.GetAwaiter().GetResult(); + } + else + { + if (tryConnectAsyncIPv4.IsCompletedSuccessfully) + { + cancelIPv6.Cancel(); + return tryConnectAsyncIPv4.GetAwaiter().GetResult(); + } + + return tryConnectAsyncIPv6.GetAwaiter().GetResult(); + } } - private static async ValueTask AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) + private static async Task AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) { // The following socket constructor will create a dual-mode socket on systems where IPV6 is available. var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) From 6621121c1b958f0e3726f3b4f1d9c2604e398504 Mon Sep 17 00:00:00 2001 From: Stefan Hendricks <38368299+Neuheit@users.noreply.github.com> Date: Sat, 29 Oct 2022 18:54:11 -0400 Subject: [PATCH 4/8] Add .NET MIT License --- .../HappyEyeballs/HttpClientExtension.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index 049c8dd150..bab02505dc 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -1,3 +1,29 @@ +/* +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + using System.IO; using System.Net.Http; using System.Net.Sockets; @@ -18,8 +44,6 @@ namespace Jellyfin.Networking.HappyEyeballs /// public static bool UseIPv6 { get; set; } = true; - // Implementation taken from https://github.com/rmkerr/corefx/blob/SocketsHttpHandler_Connect_HappyEyeballs/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs - /// /// Implements the httpclient callback method. /// From 9e791a92c3dbf12420ec13e8c0b15f70d4dcba78 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 8 Dec 2022 00:16:18 -0500 Subject: [PATCH 5/8] Add comments explaining GetAwaiter() --- Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index bab02505dc..0ceaeaf968 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -69,6 +69,9 @@ namespace Jellyfin.Networking.HappyEyeballs using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token); + //Both connect tasks use GetAwaiter().GetResult() as the appropriate task has already been completed. + //This results in improved exception handling. + //See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details. if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6) { if (tryConnectAsyncIPv6.IsCompletedSuccessfully) From a7fc5e6e12e2330344640c92cc4d0afc36c74fe2 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 8 Dec 2022 00:17:46 -0500 Subject: [PATCH 6/8] Add additional awaiter code comment. --- Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index 0ceaeaf968..add70c9cd6 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -60,6 +60,9 @@ namespace Jellyfin.Networking.HappyEyeballs using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token); + //This connect task uses GetAwaiter().GetResult() as the appropriate task has already been completed. + //This results in improved exception handling. + //See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details. if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully) { cancelIPv6.Cancel(); @@ -71,7 +74,6 @@ namespace Jellyfin.Networking.HappyEyeballs //Both connect tasks use GetAwaiter().GetResult() as the appropriate task has already been completed. //This results in improved exception handling. - //See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details. if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6) { if (tryConnectAsyncIPv6.IsCompletedSuccessfully) From 292c4ebe72d293184f7a60f68f5ad76296539b89 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 8 Dec 2022 16:15:36 -0500 Subject: [PATCH 7/8] Clarify code comment. Co-authored-by: Claus Vium --- Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index add70c9cd6..ddbf841b07 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -72,8 +72,6 @@ namespace Jellyfin.Networking.HappyEyeballs using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token); - //Both connect tasks use GetAwaiter().GetResult() as the appropriate task has already been completed. - //This results in improved exception handling. if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6) { if (tryConnectAsyncIPv6.IsCompletedSuccessfully) From 93fe47c7cb7a3aa5eb8504edc2fd4f27c4824993 Mon Sep 17 00:00:00 2001 From: Neuheit <38368299+Neuheit@users.noreply.github.com> Date: Thu, 8 Dec 2022 16:16:06 -0500 Subject: [PATCH 8/8] Clarify code comment. Co-authored-by: Claus Vium --- Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs index ddbf841b07..59e6956c71 100644 --- a/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs +++ b/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs @@ -60,9 +60,9 @@ namespace Jellyfin.Networking.HappyEyeballs using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token); - //This connect task uses GetAwaiter().GetResult() as the appropriate task has already been completed. - //This results in improved exception handling. - //See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details. + // GetAwaiter().GetResult() is used instead of .Result as this results in improved exception handling. + // The tasks have already been completed. + // See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details. if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully) { cancelIPv6.Cancel();