From 232cfcb42f55b88fe820a681878dbb84fe4c925c 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 Co-Authored-By: taloth --- .../Extensions/Pipelines/GZipPipeline.cs | 34 ++++++++++++++----- .../Http/Dispatchers/ManagedHttpDispatcher.cs | 31 ++++++++++++++--- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/Lidarr.Http/Extensions/Pipelines/GZipPipeline.cs b/src/Lidarr.Http/Extensions/Pipelines/GZipPipeline.cs index 2a31a711a..26d27aacb 100644 --- a/src/Lidarr.Http/Extensions/Pipelines/GZipPipeline.cs +++ b/src/Lidarr.Http/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 Lidarr.Http.Extensions.Pipelines @@ -15,9 +16,14 @@ namespace Lidarr.Http.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 Lidarr.Http.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 Lidarr.Http.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 370e98a21..fcd1d2403 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,10 +26,19 @@ 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); @@ -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) {