hardkoded / puppeteer-sharp

Headless Chrome .NET API
https://www.puppeteersharp.com
MIT License
3.36k stars 440 forks source link

Google Chrome for Testing - Multiple Instances on my Server #2644

Open darkkevind opened 4 months ago

darkkevind commented 4 months ago

Hi, Using PuppeteerSharp (12.0.0) with an ASP.NET Core 3.1 web application. It's deployed to my Windows 2022 server running in IIS.

I'm not 100% sure, but it seems like, every time I create a PDF document, a new instance of "Google Chrome for Testing" starts on the server, and each instance can take up a lot of RAM. After a day of web application use and PDF creation, there could be up to 20+ instances running on my server, taking up RAM and killing my SQL queries.

Any ideas on this? Anyone else had the same issue? How do I stop this happening? Is there an updated version I could use with .NET Core 3.1?

Thanks for your help.

minhquangkid commented 3 months ago

Me too! I don't know how to stop it, I generate image from HTML, here is my code : var browserFetcher = new BrowserFetcher(); await browserFetcher.DownloadAsync();

var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); var page = await browser.NewPageAsync();

await page.SetViewportAsync(new ViewPortOptions { Width = 500, Height = 500, DeviceScaleFactor = 2 // Enlarge the image size by 2 times so that it will be 1000x1000 });

await page.SetContentAsync(content);

await using (var memStream = new MemoryStream()) { await using (var screenshotStream = await page.ScreenshotStreamAsync()) { await screenshotStream.CopyToAsync(memStream); memStream.Position = 0;

    var imageName = RandomString(10);
    var formFile = new FormFile(memStream, 0, memStream.Length, null, $"{imageName}.jpg");

    using (var serviceScope = _serviceProvider.GetService<IServiceScopeFactory>().CreateScope())
    {
        var uploadFileService = serviceScope.ServiceProvider.GetService<IUploadFileService>();
        imageUrl = await uploadFileService.UploadImageAndGetUrl(formFile, "marketReport");

        return new ServiceResult { Response = imageUrl };
    }
}

}

hallzhallz commented 1 month ago

You have to dispose of the browser object for it to close the browser instance. Have you tried wrapping the browser instance in a using statement as per the example code?

using (var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});) {
// your processing code here
}
ispysoftware commented 1 month ago

Seeing the same thing here. When I close my app i'm left with orphaned processes that never close (have to End Task them in task manager) - windows 11

image

code is below, CloseAsync is called and no errors

 string[] Args = new[] {"--no-zygote", "--no-sandbox" };
 using (var b = await Puppeteer.LaunchAsync(new LaunchOptions
 {
     ExecutablePath = exePath,
     Timeout = _requestTimeout,
     Headless = true,
     IgnoreHTTPSErrors = true,
     Args = Args
 }))
 {

     using (var page = await b.NewPageAsync())
     {
     ...
     }

    await b.CloseAsync();
}
minhquangkid commented 1 month ago

@ispysoftware thank you! It helps me a lot

hallzhallz commented 1 month ago

@ispysoftware It looks like you might need to ensure all pages/tabs in the browser are closed before closing the browser, See https://github.com/puppeteer/puppeteer/issues/2269 and https://stackoverflow.com/a/76505750/9864003. Keep in mind a page (javascript) or chrome itself may spawn an additional page/tab that you did not create explictely in your code.

The following code seems to succesfully close the chrome processes for me (I still have using statements around this):

foreach (var page in await browser.PagesAsync()) {
    await page.CloseAsync();
}
await page.CloseAsync();

It appears the Async versions need to be called as well, as I had using statements on all pages and browser instances but the chrome processes would still remain for me. I am not sure if there is an easy way to recover if your process stops unexpectedly (eg if running from a website) and you get left with zombie chrome instances.

ispysoftware commented 1 month ago

Thanks @hallzhallz that seems to have resolved it. Code now looks like this and so far hasn't left any zombie processes. Looking at the page instances it looks like chrome is creating a blank tab for each started browser page.

using (var b = await Puppeteer.LaunchAsync(new LaunchOptions
{
    ExecutablePath = exePath,
    Timeout = _requestTimeout,
    Headless = true,
    IgnoreHTTPSErrors = true,
    Args = Args
}))
{

    using (var page = await b.NewPageAsync())
    {
       ...
        await page.CloseAsync();
    }
    foreach (var page in await b.PagesAsync())
    {
        if (!page.IsClosed)
            await page.CloseAsync();
    }

    if (!b.IsClosed)
        await b.CloseAsync();
}