puppeteer-sharp icon indicating copy to clipboard operation
puppeteer-sharp copied to clipboard

How to properly dispose disconnected remote instance

Open kblok opened this issue 2 years ago • 7 comments

Discussed in https://github.com/hardkoded/puppeteer-sharp/discussions/2063

Originally posted by DimitarKrastev January 9, 2023 In the usual case it is kind of straight forward how to deal with puppeteer sharp IBrowser. Tabs and browsers that are no longer needed should be disposed and the browsers we want to keep for later should only be disconnected.

The question however is what should be done once a remote browser dies during tab creation (maybe remote host went down).

On one hand standard coding practice is to dispose all disposable resources used in the code. On the other, the documentation for "Disconnect()" method says "Disconnects Puppeteer from the browser, but leaves the process running. After calling Disconnect, the browser object is considered disposed and cannot be used anymore"

So if a remote browser gets disconnected because of an exception (but not by calling Disconnect() explicitly), should we still call Dispose() or skip it?

The reason I am asking is is that today we experienced an outage on some of our browser servers which caused an Unhandled exception in our .Net 7 application crashing with Semaphore already disposed exception.

I started troubleshooting the whole lifecycle of IBrowser objects and so far this is the only clue I have. If the object was already disposed because of the disconnect exception, later on when we call Dispose() on the object might explain why we got the exception.

Thanks.

kblok avatar Jan 09 '23 13:01 kblok

@DimitarKrastev I'm unable to reproduce this issue. Could you provide an example? Here are some tests that are passing at the moment.

kblok avatar Jan 09 '23 13:01 kblok

Thats the thing, I've been trying to reproduce it, unsuccessfully for now. It was during a high load test using 50 threads with 50 open tabs spread across 25 browsers.

DimitarKrastev avatar Jan 09 '23 17:01 DimitarKrastev

What is the callstack for the exception you are seeing?

amaitland avatar Jan 09 '23 20:01 amaitland

Hey @kblok @DimitarKrastev, just sharing that I am hitting a similar exception in my application with the latest version 11.0.0. Each place I am using browser instance, I have it wrapped in an async using block to ensure they each get disposed.

I will also add that I am using this in a parallelized environment with ~16 browser instances (each with a single tab) running in parallel).

As a quick sanity check, is my approach here in the using blocks the correct approach? Or am I missing a step?

        await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions);
        await using var page = await browser.NewPageAsync();
System.ObjectDisposedException: The semaphore has been disposed.
  File "SemaphoreSlim.cs", line 918, in void SemaphoreSlim.CheckDispose()
    throw new ObjectDisposedException(null, SR.SemaphoreSlim_Disposed);
  File "SemaphoreSlim.cs", line 635, in Task<bool> SemaphoreSlim.WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken)
    }
  File "/home/runner/work/puppeteer-sharp/puppeteer-sharp/lib/PuppeteerSharp/Helpers/TaskQueue.cs", line 73, col 9, in async Task TaskQueue.Enqueue(Func<Task> taskGenerator)
  File "/home/runner/work/puppeteer-sharp/puppeteer-sharp/lib/PuppeteerSharp/Connection.cs", line 246, col 16, in async void Connection.Transport_MessageReceived(object sender, MessageReceivedEventArgs e)
  File "Task.cs", line 1929, in void Task.ThrowAsync(Exception exception, SynchronizationContext targetContext)+(object state) => { }
    ThreadPool.QueueUserWorkItem(static state => ((ExceptionDispatchInfo)state!).Throw(), edi);
  File "ThreadPoolWorkQueue.cs", line 1219, in void QueueUserWorkItemCallback.Execute()
    ExecutionContext.RunForThreadPoolUnsafe(_context, s_executionContextShim, this);
  File "ThreadPoolWorkQueue.cs", line 984, in bool ThreadPoolWorkQueue.Dispatch()
    workQueue.RefreshLoggingEnabled();
  File "PortableThreadPool.WorkerThread.cs", line 171, in void WorkerThread.WorkerThreadStart()
    }

lucaswalter avatar Aug 17 '23 13:08 lucaswalter

@lucaswalter when are you getting that exception? On dispose? on close?

kblok avatar Aug 17 '23 21:08 kblok

Getting this on dispose

lucaswalter avatar Aug 18 '23 05:08 lucaswalter

@lucaswalter v11.0.1 is building. Could you give it a try when it's available?

kblok avatar Aug 23 '23 13:08 kblok