From ce59db528b95a77c015c37df7d0f786b34592290 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Tue, 25 Dec 2018 00:22:35 +0100 Subject: [PATCH] Fixed: Mono bug causing memory leakage when http connections use gzip compression. The bug is registered upstream, but this commit works around the problem by doing the gzip decompression separately from the http stack. Ref #2296 --- .../Extensions/Pipelines/GZipPipeline.cs | 34 ++++++++++++++----- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 33 +++++++++++++++--- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs b/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs index 8aa9f4ad2..2366b80ac 100644 --- a/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs +++ b/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs @@ -5,6 +5,7 @@ using System.Linq; using Nancy; using Nancy.Bootstrapper; using NLog; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; namespace NzbDrone.Api.Extensions.Pipelines @@ -15,9 +16,14 @@ namespace NzbDrone.Api.Extensions.Pipelines public int Order => 0; + private readonly Action, Stream> _writeGZipStream; + public GzipCompressionPipeline(Logger logger) { _logger = logger; + + // On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case. + _writeGZipStream = PlatformInfo.IsMono ? WriteGZipStreamMono : (Action, Stream>)WriteGZipStream; } public void Register(IPipelines pipelines) @@ -43,14 +49,7 @@ namespace NzbDrone.Api.Extensions.Pipelines var contents = response.Contents; response.Headers["Content-Encoding"] = "gzip"; - response.Contents = responseStream => - { - using (var gzip = new GZipStream(responseStream, CompressionMode.Compress, true)) - using (var buffered = new BufferedStream(gzip, 8192)) - { - contents.Invoke(buffered); - } - }; + response.Contents = responseStream => _writeGZipStream(contents, responseStream); } } @@ -61,6 +60,25 @@ namespace NzbDrone.Api.Extensions.Pipelines } } + private static void WriteGZipStreamMono(Action innerContent, Stream targetStream) + { + using (var membuffer = new MemoryStream()) + { + WriteGZipStream(innerContent, membuffer); + membuffer.Position = 0; + membuffer.CopyTo(targetStream); + } + } + + private static void WriteGZipStream(Action innerContent, Stream targetStream) + { + using (var gzip = new GZipStream(targetStream, CompressionMode.Compress, true)) + using (var buffered = new BufferedStream(gzip, 8192)) + { + innerContent.Invoke(buffered); + } + } + private static bool ContentLengthIsTooSmall(Response response) { var contentLength = response.Headers.GetValueOrDefault("Content-Length"); diff --git a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs index 70ee66390..0970a332c 100644 --- a/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs +++ b/src/NzbDrone.Common/Http/Dispatchers/ManagedHttpDispatcher.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.IO.Compression; using System.Net; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; @@ -25,11 +26,20 @@ namespace NzbDrone.Common.Http.Dispatchers { var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url); - // Deflate is not a standard and could break depending on implementation. - // we should just stick with the more compatible Gzip - //http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net - webRequest.AutomaticDecompression = DecompressionMethods.GZip; - + if (PlatformInfo.IsMono) + { + // On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case. + webRequest.AutomaticDecompression = DecompressionMethods.None; + webRequest.Headers.Add("Accept-Encoding", "gzip"); + } + else + { + // Deflate is not a standard and could break depending on implementation. + // we should just stick with the more compatible Gzip + //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; @@ -107,6 +117,19 @@ namespace NzbDrone.Common.Http.Dispatchers try { data = responseStream.ToBytes(); + + if (PlatformInfo.IsMono && httpWebResponse.ContentEncoding == "gzip") + { + using (var compressedStream = new MemoryStream(data)) + using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress)) + using (var decompressedStream = new MemoryStream()) + { + gzip.CopyTo(decompressedStream); + data = decompressedStream.ToArray(); + } + + httpWebResponse.Headers.Remove("Content-Encoding"); + } } catch (Exception ex) {