diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 6397f11a5..9ca1d9e1d 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; namespace NzbDrone.Common.Http.Dispatchers { @@ -13,12 +17,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) @@ -38,7 +46,7 @@ namespace NzbDrone.Common.Http.Dispatchers //http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net webRequest.AutomaticDecompression = DecompressionMethods.GZip; } - + webRequest.Method = request.Method.ToString(); webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent); webRequest.KeepAlive = request.ConnectionKeepAlive; @@ -78,6 +86,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) { @@ -192,5 +203,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 429388a3e..7ecf3d061 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -23,8 +23,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 SonarrCloudRequestBuilder());