4

I am using IronPDF to generate PDF documents. My program is a web app hosted on IIS that uses ChromePdfRenderer to render PDF using an HTML string, and it sequentially generates a bunch of documents. I noticed over time, total memory used by the process increases and ultimately crashes. Further investigation showed the unmanaged memory keeps increasing as it generates PDFs.

I wrote a sample console app to replicate this

public class PdfBenchmark{

    private static readonly ChromePdfRenderOptions ChromePdfRenderOptions = new ChromePdfRenderOptions
    {
        PaperSize = IronPdf.Rendering.PdfPaperSize.Letter,
        CssMediaType = IronPdf.Rendering.PdfCssMediaType.Screen,
        PrintHtmlBackgrounds = true,
        FitToPaper = true,

        EnableJavaScript = true,
        RenderDelay = 200,
        Timeout = 60
    };

    public async Task<Stream> SimplePdf()
    {            
        var renderer = new IronPdf.ChromePdfRenderer()
        {
            RenderingOptions = ChromePdfRenderOptions
        };

        renderer.RenderingOptions.HtmlHeader.MaxHeight = 25;
        renderer.RenderingOptions.HtmlHeader.DrawDividerLine = false;
        renderer.RenderingOptions.HtmlHeader.HtmlFragment = HeaderHTML;

        renderer.RenderingOptions.HtmlFooter.MaxHeight = 12;
        renderer.RenderingOptions.HtmlFooter.DrawDividerLine = true;
        renderer.RenderingOptions.HtmlFooter.HtmlFragment = FooterHTML;


        using (var x = await renderer.RenderHtmlAsPdfAsync("<h1>Html with CSS and Images and js</h1>"))
        {
            return x.Stream;
        }                
    }


    public const string FooterHTML = @"<div class='container-fluid'>some html</div>";

    public const string HeaderHTML = @"<div class='container'>
                                <div class='row header d-flex justify-content-between'>
                                    <div class='col-md-1 logo'>
                                    </div>
                                    <div class='col text-end right'>
                                        <div>>some HTML content</div>
                                    </div>
                                </div>
                            </div>";

    }
}

static async Task Main(string[] args)
{            
    var x = new PdfBenchmark();
    for (int i = 0; i < 200; i++)
    {
        using var stream = await x.SimplePdf();
        //Do whatever I want here...
        Console.WriteLine($"Created doc#: {i}");
    }
}

the larger the HTML, the quicker the memory usage grows. Here's the memory profiler snapshot.

dotMemory profile

You can see how unmanaged memory grew fast, but wasn't released after about 100 iterations. I've reproduced this on .NET Core 3.1 and .NET Framework.

Has anyone else had this issue and how did you resolve this?

npp
  • 116
  • 7
  • With your profiler, you want to examine the objects and track down what is taking the space. Otherwise, may I suggest pdfsharpcore wich can be updated to .net 5.0 ? – Soleil Nov 05 '21 at 14:27
  • 3
    You are not disposing stream. Of course it will grow. If something disposable - dispose it, be on the safe side. Btw, IronPDF not even downloading from nuget, so no way to check this at the moment. – eocron Nov 05 '21 at 14:27
  • 1
    @eocron MemoyStream is a managed object. GC disposes it, does it not?. What's growing is the unmanaged memory. – npp Nov 05 '21 at 14:30
  • 1
    There is no MemoryStream in your code. So no way for me to know, maybe it is FileStream. You specify Stream type - it is disposable, and you should dispose it. Even if it returns MemoryStream at the moment. – eocron Nov 05 '21 at 14:32
  • 1
    Try to cache your renderer - my assumption is that it creates separate process and invoke IPC, so this can be a problem. Documentation is obscure on IronPDF so I assume it is another DIY piece of garbage framework with lack of documentation/open sources, enforce paid support and do not provide tech support and should be used carefully. Chromium is pretty universal thing and can be used directly to generate pdf without this garbage - https://stackoverflow.com/questions/53324060/with-c-sharp-use-chrome-to-covert-html-to-pdf – eocron Nov 05 '21 at 14:38
  • 3
    @npp. The GC does not _Dispose_ stuff. Only user code deals with `IDisposable`. The GC will _Finalize_ things, but there are restrictions about what can happen within a Finalizer. In particular, I remember that there were problems with the interactions between Files and Streams (way back when .NET first shipped - don't know if they're still there) that caused issues if `Dispose` was not called when it was supposed to. The rule (from eocron) above: _"If something disposable - dispose it"_ is a *very* good rule – Flydog57 Nov 05 '21 at 14:51
  • disposing the stream doesn't make a difference. I've tried that and still see the issue. Looks like IronPDF is using some unmanaged resources internally and not releasing them properly – npp Nov 06 '21 at 13:26
  • 1
    Since a lot of people think not disposing the memory stream is the problem, I updated my code snippet above. Current implementation does not cause any memory leaks because of the MemoryStream, unless it's doing something else once the response is received – npp Nov 08 '21 at 14:24

2 Answers2

3

IronPDF acknowledged there was a memory leak in their Chrome renderer and they've fixed the issue. Fix is available from version 2021.11.4257 onwards.

npp
  • 116
  • 7
2

The memory leak is resolved in IronPdf update 2021.11.4257, released 2021-11-15. https://www.nuget.org/packages/IronPdf/2021.11.4257

  • Fixes memory leaks (see attached images below)
  • Adds Installation.ChromeBrowserLimit
  • Adds AWS support. Full Docker setup tutorial here - https://iron.helpscoutdocs.com/article/115-aws-lambda-amazon-linux-2 (this does not yet include support for AWS "Layers")
  • Updated exception messages, to allow developers to diagnose more issues
  • Several minor bug fixes

What will be in our next update? (known issues):

Applying headers and footers to a document with a page count greater than 30 currently causes an issue, so to successfully apply headers and footers to documents with a page count greater than 30, you must add IronPdf.Installation.ChromeBrowserLimit = 50; where "50" is greater than the expected page count of your large document.

How long was there a memory leak?

Around November 1st in IronPdf 2021.11.4183

Why did this occur?

Our Chrome renderer is relatively new unmanaged propriety code. A recent update caused a memory leak which was not captured due to inadequate extended performance profiling.

How will this be prevented in the future?

We have implemented extended performance profiling for our "continuous integration" process to ensure that both our managed and unmanaged code performance is up-to-par before releasing IronPdf to the public.

2 files Confirmed memory leak in 2021.11.4183Memory leak FIXED in 2021.11.4257

darren
  • 475
  • 4
  • 15
  • Looks like you pulled the 2021.11.4183 version from nuget. I would look into version 2021.9.3737 also. My example above was produced using that version and I believe that's where the ChromePdfRenderer was introduced? Thanks for the update though – npp Nov 18 '21 at 02:08
  • We'll look in to that. The Chrome renderer was introduced in an earlier version, https://www.nuget.org/packages/IronPdf/2021.9.3678 – darren Nov 19 '21 at 07:28