puppeteer-sharp
puppeteer-sharp copied to clipboard
How to properly dispose disconnected remote instance
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.
@DimitarKrastev I'm unable to reproduce this issue. Could you provide an example? Here are some tests that are passing at the moment.
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.
What is the callstack for the exception you are seeing?
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 when are you getting that exception? On dispose? on close?
Getting this on dispose
@lucaswalter v11.0.1 is building. Could you give it a try when it's available?