From d1bcfeab1c95ae95de02d672d0fe7e1c61d5bc05 Mon Sep 17 00:00:00 2001 From: Oleg Stepanischev Date: Mon, 14 Nov 2022 17:23:04 +0300 Subject: [PATCH] 0.6.5: Prevent context release during compression/decompression if Dispose is not called --- src/ZstdSharp/Compressor.cs | 22 +++++++++++++++++++--- src/ZstdSharp/Decompressor.cs | 20 ++++++++++++++++++-- src/ZstdSharp/ZstdSharp.csproj | 2 +- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/ZstdSharp/Compressor.cs b/src/ZstdSharp/Compressor.cs index 25d2607..6f902f6 100644 --- a/src/ZstdSharp/Compressor.cs +++ b/src/ZstdSharp/Compressor.cs @@ -11,6 +11,12 @@ public unsafe class Compressor : IDisposable private int level = DefaultCompressionLevel; + /* + * We have a finalizer that releases cctx (to prevent memory leaks if Disposed is not called), + * so we need to delay running the object's finalizer when dealing with cctx inside our methods. + * For this purpose we use GC.KeepAlive(this) + * For reference: https://devblogs.microsoft.com/oldnewthing/20100813-00/?p=13153 + */ private ZSTD_CCtx_s* cctx; public int Level @@ -30,6 +36,7 @@ public void SetParameter(ZSTD_cParameter parameter, int value) { EnsureNotDisposed(); Methods.ZSTD_CCtx_setParameter(cctx, parameter, value).EnsureZstdSuccess(); + GC.KeepAlive(this); } public int GetParameter(ZSTD_cParameter parameter) @@ -37,13 +44,14 @@ public int GetParameter(ZSTD_cParameter parameter) EnsureNotDisposed(); int value; Methods.ZSTD_CCtx_getParameter(cctx, parameter, &value).EnsureZstdSuccess(); + GC.KeepAlive(this); return value; } public void LoadDictionary(byte[] dict) { var dictReadOnlySpan = new ReadOnlySpan(dict); - this.LoadDictionary(dictReadOnlySpan); + LoadDictionary(dictReadOnlySpan); } public void LoadDictionary(ReadOnlySpan dict) @@ -59,6 +67,7 @@ public void LoadDictionary(ReadOnlySpan dict) fixed (byte* dictPtr = dict) Methods.ZSTD_CCtx_loadDictionary(cctx, dictPtr, (nuint) dict.Length).EnsureZstdSuccess(); } + GC.KeepAlive(this); } public Compressor(int level = DefaultCompressionLevel) @@ -96,9 +105,13 @@ public int Wrap(ReadOnlySpan src, Span dest) EnsureNotDisposed(); fixed (byte* srcPtr = src) fixed (byte* destPtr = dest) - return (int) Methods + { + var returnValue = (int) Methods .ZSTD_compress2(cctx, destPtr, (nuint) dest.Length, srcPtr, (nuint) src.Length) .EnsureZstdSuccess(); + GC.KeepAlive(this); + return returnValue; + } } public int Wrap(ArraySegment src, ArraySegment dest) @@ -118,6 +131,7 @@ public bool TryWrap(ReadOnlySpan src, Span dest, out int written) { var returnValue = Methods.ZSTD_compress2(cctx, destPtr, (nuint) dest.Length, srcPtr, (nuint) src.Length); + GC.KeepAlive(this); if (returnValue == unchecked(0 - (nuint)ZSTD_ErrorCode.ZSTD_error_dstSize_tooSmall)) { @@ -163,7 +177,9 @@ internal nuint CompressStream(ref ZSTD_inBuffer_s input, ref ZSTD_outBuffer_s ou fixed (ZSTD_inBuffer_s* inputPtr = &input) fixed (ZSTD_outBuffer_s* outputPtr = &output) { - return Methods.ZSTD_compressStream2(cctx, outputPtr, inputPtr, directive).EnsureZstdSuccess(); + var returnValue = Methods.ZSTD_compressStream2(cctx, outputPtr, inputPtr, directive).EnsureZstdSuccess(); + GC.KeepAlive(this); + return returnValue; } } } diff --git a/src/ZstdSharp/Decompressor.cs b/src/ZstdSharp/Decompressor.cs index 699155d..544ff64 100644 --- a/src/ZstdSharp/Decompressor.cs +++ b/src/ZstdSharp/Decompressor.cs @@ -5,6 +5,12 @@ namespace ZstdSharp { public unsafe class Decompressor : IDisposable { + /* + * We have a finalizer that releases dctx (to prevent memory leaks if Disposed is not called), + * so we need to delay running the object's finalizer when dealing with dctx inside our methods. + * For this purpose we use GC.KeepAlive(this) + * For reference: https://devblogs.microsoft.com/oldnewthing/20100813-00/?p=13153 + */ private ZSTD_DCtx_s* dctx; public Decompressor() @@ -23,6 +29,7 @@ public void SetParameter(ZSTD_dParameter parameter, int value) { EnsureNotDisposed(); Methods.ZSTD_DCtx_setParameter(dctx, parameter, value).EnsureZstdSuccess(); + GC.KeepAlive(this); } public int GetParameter(ZSTD_dParameter parameter) @@ -30,6 +37,7 @@ public int GetParameter(ZSTD_dParameter parameter) EnsureNotDisposed(); int value; Methods.ZSTD_DCtx_getParameter(dctx, parameter, &value).EnsureZstdSuccess(); + GC.KeepAlive(this); return value; } @@ -51,6 +59,7 @@ public void LoadDictionary(ReadOnlySpan dict) fixed (byte* dictPtr = dict) Methods.ZSTD_DCtx_loadDictionary(dctx, dictPtr, (nuint) dict.Length).EnsureZstdSuccess(); } + GC.KeepAlive(this); } public static ulong GetDecompressedSize(ReadOnlySpan src) @@ -88,9 +97,13 @@ public int Unwrap(ReadOnlySpan src, Span dest) EnsureNotDisposed(); fixed (byte* srcPtr = src) fixed (byte* destPtr = dest) - return (int) Methods + { + var returnValue = (int) Methods .ZSTD_decompressDCtx(dctx, destPtr, (nuint) dest.Length, srcPtr, (nuint) src.Length) .EnsureZstdSuccess(); + GC.KeepAlive(this); + return returnValue; + } } public int Unwrap(byte[] src, int srcOffset, int srcLength, byte[] dst, int dstOffset, int dstLength) @@ -107,6 +120,7 @@ public bool TryUnwrap(ReadOnlySpan src, Span dest, out int written) { var returnValue = Methods.ZSTD_decompressDCtx(dctx, destPtr, (nuint) dest.Length, srcPtr, (nuint) src.Length); + GC.KeepAlive(this); if (returnValue == unchecked(0 - (nuint)ZSTD_ErrorCode.ZSTD_error_dstSize_tooSmall)) { @@ -148,7 +162,9 @@ internal nuint DecompressStream(ref ZSTD_inBuffer_s input, ref ZSTD_outBuffer_s fixed (ZSTD_inBuffer_s* inputPtr = &input) fixed (ZSTD_outBuffer_s* outputPtr = &output) { - return Methods.ZSTD_decompressStream(dctx, outputPtr, inputPtr).EnsureZstdSuccess(); + var returnValue = Methods.ZSTD_decompressStream(dctx, outputPtr, inputPtr).EnsureZstdSuccess(); + GC.KeepAlive(this); + return returnValue; } } } diff --git a/src/ZstdSharp/ZstdSharp.csproj b/src/ZstdSharp/ZstdSharp.csproj index f5adb02..b554793 100644 --- a/src/ZstdSharp/ZstdSharp.csproj +++ b/src/ZstdSharp/ZstdSharp.csproj @@ -14,7 +14,7 @@ https://github.com/oleg-st/ZstdSharp false zstd zstandard port compression - 0.6.4 + 0.6.5