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
impl BrowserManager
Sourcepub fn global() -> Arc<BrowserManager>
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?;Sourcepub async fn get_or_launch(&self) -> Result<Arc<Mutex<Option<BrowserWrapper>>>>
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
- Lock browser mutex
- If browser exists, check health via version() CDP command
- If unhealthy, close crashed browser and remove from cache
- If no browser or was unhealthy, launch new instance
- 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?;
}Sourcepub async fn shutdown(&self) -> Result<()>
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:
browser.close().await- Sends close command to Chromebrowser.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
Sourcepub async fn is_browser_running(&self) -> bool
pub async fn is_browser_running(&self) -> bool
Check if browser is currently running
Non-blocking check of browser state.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for BrowserManager
impl !NotResult for BrowserManager
impl !NotString for BrowserManager
impl !RefUnwindSafe for BrowserManager
impl Send for BrowserManager
impl Sync for BrowserManager
impl Unpin for BrowserManager
impl !UnwindSafe for BrowserManager
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> GetSetFdFlags for T
impl<T> GetSetFdFlags for T
Source§fn get_fd_flags(&self) -> Result<FdFlags, Error>where
T: AsFilelike,
fn get_fd_flags(&self) -> Result<FdFlags, Error>where
T: AsFilelike,
self file descriptor.Source§fn new_set_fd_flags(&self, fd_flags: FdFlags) -> Result<SetFdFlags<T>, Error>where
T: AsFilelike,
fn new_set_fd_flags(&self, fd_flags: FdFlags) -> Result<SetFdFlags<T>, Error>where
T: AsFilelike,
Source§fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<T>) -> Result<(), Error>where
T: AsFilelike,
fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<T>) -> Result<(), Error>where
T: AsFilelike,
self file descriptor. Read moreSource§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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 moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request