BrowserManager

Struct BrowserManager 

Source
pub struct BrowserManager { /* private fields */ }
Expand description

Singleton manager for browser instances with health checking and crash recovery

Manages browser lifecycle to ensure:

  • Only one browser instance exists at a time (lazy-loaded)
  • Automatic launch on first use (~2-3s first call, instant after)
  • Health checking on every access to detect crashes
  • Automatic crash recovery (transparent to callers)
  • Thread-safe access from multiple tools
  • Proper cleanup when dropped or shutdown

§Performance Characteristics

  • First get_or_launch(): ~2-3 seconds (launches Chrome)
  • Subsequent calls (healthy browser): ~10-50ms (health check + mutex lock)
  • Recovery from crash: ~2-3 seconds (detects + closes + re-launches)
  • Memory: ~150MB per browser instance (Chrome process)

§Health Checking and Crash Recovery

Every call to get_or_launch() performs a health check via browser.version() CDP command. If the browser has crashed, it is automatically cleaned up and a new instance is launched. This provides transparent recovery without requiring MCP server restarts.

§Pattern Source

Based on: packages/tools-citescrape/src/web_search/manager.rs:14-122

Implementations§

Source§

impl BrowserManager

Source

pub fn global() -> Arc<BrowserManager>

Get the global singleton BrowserManager instance

This ensures only one browser instance runs process-wide. All tools should use this method instead of creating their own managers.

§Thread Safety

Uses OnceLock for atomic initialization - safe to call from multiple threads. First caller initializes, concurrent callers block until initialization completes.

§Performance
  • First call: ~50ns (creates BrowserManager struct)
  • Subsequent calls: ~5ns (atomic pointer load)
  • Browser launch still lazy on first get_or_launch() call (~2-3s)
§Example
let manager = BrowserManager::global();
let browser_arc = manager.get_or_launch().await?;
Source

pub async fn get_or_launch(&self) -> Result<Arc<Mutex<Option<BrowserWrapper>>>>

Get or launch the shared browser instance with health checking and auto-recovery

§Health Check and Recovery Flow
  1. Lock browser mutex
  2. If browser exists, check health via version() CDP command
  3. If unhealthy, close crashed browser and remove from cache
  4. If no browser or was unhealthy, launch new instance
  5. Return healthy browser
§First Call
  • ~2-3s (launches browser)
§Subsequent Calls (healthy browser)
  • <1ms (mutex lock + Arc clone)
§Recovery from Crash
  • ~2-3s (detects crash + closes + re-launches)
  • Automatic, no user intervention required
§Returns

Arc to the browser Mutex - caller locks it to access BrowserWrapper

§Example
let manager = BrowserManager::global();
let browser_arc = manager.get_or_launch().await?;
let browser_guard = browser_arc.lock().await;
if let Some(wrapper) = browser_guard.as_ref() {
    let page = wrapper.browser().new_page("https://httpbin.org/html").await?;
}
Source

pub async fn shutdown(&self) -> Result<()>

Shutdown the browser if running

Explicitly closes the browser process and cleans up resources. Safe to call multiple times (subsequent calls are no-ops).

§Critical Implementation Note

We must call BOTH:

  1. browser.close().await - Sends close command to Chrome
  2. browser.wait().await - Waits for process to fully exit

WHY: BrowserWrapper::drop() only aborts the handler task. It does NOT close the browser process. Without explicit close(), Chrome process becomes a zombie and logs warnings.

§Example from citescrape
// packages/tools-citescrape/src/web_search/manager.rs:99-114
if let Some(mut wrapper) = browser_lock.take() {
    info!("Shutting down browser");
     
    // 1. Close the browser
    if let Err(e) = wrapper.browser_mut().close().await {
        tracing::warn!("Failed to close browser cleanly: {}", e);
    }
     
    // 2. Wait for process to fully exit
    if let Err(e) = wrapper.browser_mut().wait().await {
        tracing::warn!("Failed to wait for browser exit: {}", e);
    }
     
    // 3. Now drop the wrapper (calls handler.abort())
    drop(wrapper);
}

Based on: packages/tools-citescrape/src/web_search/manager.rs:88-122

Source

pub async fn is_browser_running(&self) -> bool

Check if browser is currently running

Non-blocking check of browser state.

Trait Implementations§

Source§

impl Drop for BrowserManager

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<U> As for U

Source§

fn as_<T>(self) -> T
where T: CastFrom<U>,

Casts self to type T. The semantics of numeric casting with the as operator are followed, so <T as As>::as_::<U> can be used in the same way as T as U for numeric conversions. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> GetSetFdFlags for T

Source§

fn get_fd_flags(&self) -> Result<FdFlags, Error>
where T: AsFilelike,

Query the “status” flags for the self file descriptor.
Source§

fn new_set_fd_flags(&self, fd_flags: FdFlags) -> Result<SetFdFlags<T>, Error>
where T: AsFilelike,

Create a new SetFdFlags value for use with set_fd_flags. Read more
Source§

fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<T>) -> Result<(), Error>
where T: AsFilelike,

Set the “status” flags for the self file descriptor. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Pointee for T

Source§

type Pointer = u32

Source§

fn debug( pointer: <T as Pointee>::Pointer, f: &mut Formatter<'_>, ) -> Result<(), Error>

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<G1, G2> Within<G2> for G1
where G2: Contains<G1>,

Source§

fn is_within(&self, b: &G2) -> bool

Source§

impl<G1, G2> Within<G2> for G1
where G2: Contains<G1>,

Source§

fn is_within(&self, b: &G2) -> bool

Source§

impl<G1, G2> Within<G2> for G1
where G2: Contains<G1>,

Source§

fn is_within(&self, b: &G2) -> bool

Source§

impl<T> ErasedDestructor for T
where T: 'static,