diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index fcd1d2403..f32ac8845 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -2,9 +2,13 @@ using System; using System.IO; using System.IO.Compression; using System.Net; +using System.Reflection; +using NLog; +using NLog.Fluent; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http.Proxy; +using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Security; namespace NzbDrone.Common.Http.Dispatchers @@ -14,12 +18,16 @@ namespace NzbDrone.Common.Http.Dispatchers private readonly IHttpProxySettingsProvider _proxySettingsProvider; private readonly ICreateManagedWebProxy _createManagedWebProxy; private readonly IUserAgentBuilder _userAgentBuilder; + private readonly IPlatformInfo _platformInfo; + private readonly Logger _logger; - public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy, IUserAgentBuilder userAgentBuilder) + public ManagedHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, ICreateManagedWebProxy createManagedWebProxy, IUserAgentBuilder userAgentBuilder, IPlatformInfo platformInfo, Logger logger) { _proxySettingsProvider = proxySettingsProvider; _createManagedWebProxy = createManagedWebProxy; _userAgentBuilder = userAgentBuilder; + _platformInfo = platformInfo; + _logger = logger; } public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies) @@ -84,6 +92,9 @@ namespace NzbDrone.Common.Http.Dispatchers if (httpWebResponse == null) { + // Workaround for mono not closing connections properly in certain situations. + AbortWebRequest(webRequest); + // The default messages for WebException on mono are pretty horrible. if (e.Status == WebExceptionStatus.NameResolutionFailure) { @@ -198,5 +209,36 @@ namespace NzbDrone.Common.Http.Dispatchers } } } + + // Workaround for mono not closing connections properly on timeouts + private void AbortWebRequest(HttpWebRequest webRequest) + { + // First affected version was mono 5.16 + if (OsInfo.IsNotWindows && _platformInfo.Version >= new Version(5, 16)) + { + try + { + var currentOperationInfo = webRequest.GetType().GetField("currentOperation", BindingFlags.NonPublic | BindingFlags.Instance); + var currentOperation = currentOperationInfo.GetValue(webRequest); + + if (currentOperation != null) + { + var responseStreamInfo = currentOperation.GetType().GetField("responseStream", BindingFlags.NonPublic | BindingFlags.Instance); + var responseStream = responseStreamInfo.GetValue(currentOperation) as Stream; + // Note that responseStream will likely be null once mono fixes it. + responseStream?.Dispose(); + } + } + catch (Exception ex) + { + // This can fail randomly on future mono versions that have been changed/fixed. Log to sentry and ignore. + _logger.Trace() + .Exception(ex) + .Message("Unable to dispose responseStream on mono {0}", _platformInfo.Version) + .WriteSentryWarn("MonoCloseWaitPatchFailed", ex.Message) + .Write(); + } + } + } } } diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index 72fecad2b..98cadc6cd 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -24,8 +24,8 @@ namespace NzbDrone.Core.Test.Framework Mocker.SetConstant(new HttpProxySettingsProvider(Mocker.Resolve())); Mocker.SetConstant(new ManagedWebProxyFactory(Mocker.Resolve())); - Mocker.SetConstant(new ManagedHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve())); - Mocker.SetConstant(new CurlHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve())); + Mocker.SetConstant(new ManagedHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); + Mocker.SetConstant(new CurlHttpDispatcher(Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new HttpProvider(TestLogger)); Mocker.SetConstant(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), Mocker.Resolve(), TestLogger)); Mocker.SetConstant(new LidarrCloudRequestBuilder());