Possible Memory Leak in conversion of Spreadsheets in .net 6

Hi GemBox Team,

I have a simple application converting spreadsheets to multiple images by calling this method 15 times:

public static void ConvertFile(string inputPath, string outputDirectory, int i)
    {
        var spreadSheet = ExcelFile.Load(inputPath, new XlsxLoadOptions
        {
            StreamingMode = LoadStreamingMode.Read
        });

        var pages = spreadSheet.GetPaginator(new PaginatorOptions { SelectionType = SelectionType.EntireFile }).Pages;
        
        for (var index = 0; index < pages.Count; index++)
        {
            var fileName = $"Spreadsheet-{i}_Page-{index}.bmp";
            
            var page = pages[index];
            
            using var stream = new FileStream(Path.Combine(outputDirectory, fileName), FileMode.Create);
            
            page.Save(stream, new ImageSaveOptions(ImageSaveFormat.Bmp));
        }
    }

When I run this code in a .net472 console application, the memory consumption is the following:

However, when I change the application’s target framework to .net6 the memory consumption increases heavily:

Could there be a memory leak in one of the operations, that GemBox uses, when it is executed in .net6?

Best regards,
Sarah

Hi Sarah,

We were unable to reproduce this behavior, we tried running on “net48”, “net6.0-windows”, and “netcoreapp3.1”.

What version of GemBox.Spreadsheet are you using?
Can you try again using the current latest version?

Can you also try again with some other XLSX file, is it possible that your issue can be reproduced only with the specific Excel file?

Regards,
Mario

Hi Mario,

I updates Gembox.Spreadsheet to 49.0.1557-hotfix and changed the target framework from “.net6.0” to “.net6.0-windows” and indeed the behaviour was not reproducible.
The memory consumption:

As I plan to use .net6.0 in my application to deploy for Linux OS though I cannot change my target framework in production.
Can you reproduce the issue with using target framework “net6.0” and an excel document that has a random value in the cell AV:125?

Best regards,
Sarah

Additional note: My original document also included a larger amount of cells with background colours and some merged and centered cells with texts in it, maybe this increases the effect. The last cell is AV:125.

Sarah we were still unable to reproduce your issue.
Please send us a small Visual Studio project that reproduces this so that we can investigate it.

Hi Mario, I created a small GitHub Repo with my repro code: GitHub - SarahAmagno/ReproOOM
I hope it is reproducible with this, otherwise I am not sure what setting in my local environment could cause this behaviour.

Hi Sarah,

Sorry for the delayed answer. It took us some time to analyze this scenario.

It seems that memory increase is happening because GemBox.Spreadsheet uses SkiaSharp for rendering and SkiaSharp underneath uses the native Skia library.

It seems that either SkiaSharp or, more probably, native Skia has some caching implemented in the unmanaged memory and the size of that cache stabilizes around 1 GB.

Here is a code that we used to reproduce this behavior:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GCSettings.LatencyMode = GCLatencyMode.Batch;

var workbook = new ExcelFile();
workbook.Worksheets.Add("Sheet1").Cells["A1"].Value = "Hello world";

for (int i = 0; i < 10_000; ++i)
{
    using var stream = new MemoryStream();
    workbook.Save(stream, new ImageSaveOptions(ImageSaveFormat.Png) { RenderingMode = RenderingMode.Skia });

    if (i % 50 == 0)
    {
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true, compacting: true);

        Console.WriteLine("GC collect " + i);
    }
}

The memory increases to 1.1 GB (almost 5 minutes of runtime on my machine) and then drops to 0.9 GB. After that, it increases again to 1.1 GB and then drops again to 0.9 GB. This behavior repeats indefinitely. Forcing garbage collection doesn’t have any effect, so I conclude that this memory is occupied by the unmanaged code.

Here is a screenshot of the Visual Studio Performance Profiler:

Since memory does not increase indefinitely, we do not consider this as a memory leak.

Regards,
Stipo

Hi Stipo,

thank you for the detailed explanation.
I understand that this is no memory leak, nevertheless the memory consumption of over 1 GB for the conversion of multiple files is quite a lot - is there a possibility, to reduce it?
Can the internal cache be cleared or limited, for example?
The Skia library offers the GRContext for operations like this, but from “outside” of the GemBox package I cannot access the created image’s GRContext.

Best regards,
Sarah

Hi Sarah,

After further investigation, we were able to identify the root cause of the issue - font cache in Skia.

Here is an updated code snippet that shows how the Skia font cache increases over time and how memory usage drops when the SkiaSharp.SKGraphics.PurgeFontCache method is called:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GCSettings.LatencyMode = GCLatencyMode.Batch;

var workbook = new ExcelFile();
workbook.Worksheets.Add("Sheet1").Cells["A1"].Value = "Hello world";

for (int i = 0; i < 10_000; ++i)
{
    using var stream = new MemoryStream();
    workbook.Save(stream, new ImageSaveOptions(ImageSaveFormat.Png) { RenderingMode = RenderingMode.Skia });

    Console.WriteLine("SKGraphics.FontCacheUsed/FontCacheLimit = {0}/{1}", SkiaSharp.SKGraphics.GetFontCacheUsed(), SkiaSharp.SKGraphics.GetFontCacheLimit());

    if (i % 20 == 0)
    {
        SkiaSharp.SKGraphics.PurgeFontCache();
        Console.WriteLine("SKGraphics.PurgeFontCache " + i);
    }

    if (i % 50 == 0)
    {
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true, compacting: true);
        Console.WriteLine("GC collect " + i);
    }
}

So, currently, you can either reduce the font cache limit with the method SKGraphics.SetFontCacheLimit once or purge the font cache each time memory usage is exceeded with the method SKGraphics.PurgeFontCache.

We will also implement a quick-fix in which we plan to globally cache SKTypeface instances because it seems that the Skia font cache size is increased with each new SKTypeface instance. We will notify you when we implement this quick-fix so you can test it.

Regards,
Stipo

1 Like

Hi Sarah,

Please try again with this bugfix:

Install-Package GemBox.Spreadsheet -Version 49.0.1569-hotfix

Does this solve your issue?

Regards,
Mario

1 Like

Hi Mario and Stipo,

the fix works! Thank you, now the memory consumption looks much better:

Best regards,
Sarah