# How Camofox-Browser Implements Lazy Browser Launch and Idle Shutdown

> Learn how Camofox Browser uses lazy launch and idle shutdown to optimize resources. Discover its singleton promise pattern for efficient Firefox instance management and reduced waste.

- Repository: [jo/camofox-browser](https://github.com/jo-inc/camofox-browser)
- Tags: architecture
- Published: 2026-04-15

---

**Camofox-browser defers Firefox (Camoufox) instance startup until the first client request and automatically terminates the browser after a configurable period of inactivity, using a singleton promise pattern to serialize concurrent launches and prevent resource waste.**

The **jo-inc/camofox-browser** project provides a resource-efficient HTTP API for browser automation by keeping Firefox instances alive only when active work exists. Its **lazy browser launch and idle shutdown architecture** ensures that headless browsers start on-demand and shut down automatically when idle, minimizing memory and CPU consumption across deployments.

## Core Architecture Components

The implementation splits responsibility across three cooperating layers in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js): the launch guarantee, the instance factory, and the idle scheduler.

### Lazy Launch Guarantee with `ensureBrowser()`

Located at lines 667–694 in [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js), the **`ensureBrowser()`** function acts as the single entry point for all browser-dependent operations. It maintains a module-level **`browserLaunchPromise`** that guarantees only one launch attempt executes even when multiple concurrent requests arrive simultaneously.

If the browser instance exists but has disconnected, `ensureBrowser()` tears down all dead sessions, clears any active virtual display, and forces a fresh launch. This defensive check ensures API resilience against crashed browser processes.

### Browser Instance Creation in `launchBrowserInstance()`

The **`launchBrowserInstance()`** function performs the actual heavy lifting referenced within [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js). It selects the host OS, optionally initializes an **X-vfb virtual display**, configures proxy settings if enabled, builds Playwright launch options, and starts the Firefox (Camoufox) process.

When rotating proxies are configured, the function runs a **Google probe** with retry logic up to `maxAttempts` to verify connectivity before considering the launch successful. Upon completion, it stores the browser reference in the module-level `browser` variable and registers the display for cleanup tracking.

### Idle Shutdown Scheduling via `scheduleBrowserIdleShutdown()`

After every request completion, the server invokes **`scheduleBrowserIdleShutdown()`**. This scheduler checks whether any active sessions remain (`sessions.size === 0`). If the session map is empty, it sets a timeout using **`BROWSER_IDLE_TIMEOUT_MS`** (defaulting to 300,000 ms or 5 minutes).

When the timer fires, the system logs "browser idle shutdown (no sessions)", closes the browser instance, and clears the global reference. The companion **`clearBrowserIdleTimer()`** function resets the scheduler whenever new activity occurs, preventing premature shutdown during active use.

## Request Lifecycle Flow

Understanding the sequence clarifies how resource efficiency is achieved:

1. **First Request Arrives**: `ensureBrowser()` detects `browser` is `null` and creates a singleton `browserLaunchPromise`, then calls `launchBrowserInstance()`.
2. **Browser Initialization**: The instance launches with optional X-vfb, proxy verification, and Playwright configuration, storing the result in the global `browser` variable.
3. **Concurrent Request Handling**: Additional requests arriving during launch await the same `browserLaunchPromise`, preventing duplicate browser processes.
4. **Session Completion**: When clients close tabs or requests finish, the session map updates. `scheduleBrowserIdleShutdown()` evaluates the empty state.
5. **Idle Detection**: If `sessions.size` remains zero for the configured timeout duration, the browser closes and the reference clears.
6. **Subsequent Requests**: The next API call triggers step 1 again, lazily relaunching the browser only when needed.

## Implementation Examples

### Integrating Lazy Launch in API Routes

The following pattern from [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) demonstrates how routes leverage the architecture:

```javascript
app.post('/tabs/:tabId/navigate', async (req, res) => {
  // … validation omitted …
  try {
    // Guarantees a live browser; launches lazily if needed.
    const b = await ensureBrowser();        // ← lazy launch
    const session = await getSession(userId); // creates a new Playwright context
    const page = await session.context.newPage();
    await page.goto(req.body.url, { timeout: NAVIGATE_TIMEOUT_MS });
    // … respond with snapshot …
  } finally {
    // Ensure idle shutdown timer is refreshed after each request.
    scheduleBrowserIdleShutdown();          // ← idle‑shutdown scheduling
  }
});

```

### Manual Shutdown Control

For administrative or testing scenarios, you can force immediate cleanup bypassing the idle timer:

```javascript
// Force immediate shutdown, ignoring the idle timer.
if (browser) {
  log('info', 'manual shutdown');
  const b = browser;
  browser = null;
  await b.close();
}

```

### Subprocess Isolation with `launchServer()`

The architecture separates environment handling from the main server through **`launchServer()`** in [`lib/launcher.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/launcher.js). This wrapper spawns the HTTP API as a subprocess, ensuring [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) never reads `process.env` directly or spawns child processes other than the isolated browser instance.

```javascript
import { launchServer } from './lib/launcher.js';
const proc = launchServer({
  pluginDir: __dirname,
  port: 9377,
  env: { CAMOFOX_API_KEY: 'secret' },
  log: console,
});
proc.on('exit', code => console.log('server exited', code));

```

## Configuration and Key Files

| File | Responsibility | Key Exports/Functions |
|------|---------------|----------------------|
| [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) | Main HTTP server, lazy-launch logic, idle-shutdown scheduler | `ensureBrowser()`, `launchBrowserInstance()`, `scheduleBrowserIdleShutdown()` |
| [`lib/launcher.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/launcher.js) | Subprocess isolation and environment management | `launchServer()` |
| [`lib/config.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js) | Centralized environment configuration | `browserIdleTimeoutMs`, proxy settings, launch options |
| [`lib/metrics.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/metrics.js) | Optional Prometheus instrumentation | Lazily loaded metrics to avoid interfering with idle logic |

The **[`lib/config.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js)** module centralizes all `process.env` access, providing `browserIdleTimeoutMs` and other launch parameters without polluting the main server logic. For observability, **[`lib/metrics.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/metrics.js)** offers Prometheus instrumentation that loads lazily to avoid interfering with shutdown timing.

## Summary

- **On-demand startup**: The `ensureBrowser()` singleton promise pattern ensures Firefox launches only when the first request arrives, with concurrent callers waiting on the same launch operation.
- **Automatic cleanup**: `scheduleBrowserIdleShutdown()` monitors session counts and closes the browser after `BROWSER_IDLE_TIMEOUT_MS` (default 5 minutes) of inactivity.
- **Resource safety**: Disconnected browser detection and virtual display cleanup in `ensureBrowser()` prevent resource leaks from crashed instances.
- **Process isolation**: `launchServer()` in [`lib/launcher.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/launcher.js) separates the HTTP API from environment handling, supporting supervisor-based deployments.

## Frequently Asked Questions

### How does camofox-browser prevent race conditions during concurrent launches?

When multiple requests arrive simultaneously while no browser instance exists, **`ensureBrowser()`** creates a single `browserLaunchPromise` that all callers await. This singleton promise pattern serializes access to `launchBrowserInstance()`, ensuring only one Firefox process starts despite concurrent API requests, preventing resource waste and port conflicts.

### What configuration controls the idle shutdown timeout?

The **`BROWSER_IDLE_TIMEOUT_MS`** environment variable, centralized in [`lib/config.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/config.js), sets the milliseconds of inactivity before automatic shutdown (defaulting to 300,000 ms or 5 minutes). Adjusting this value in the environment changes how long the browser remains running after the last session closes.

### How does the architecture handle unexpected browser crashes?

The **`ensureBrowser()`** function detects disconnected browser states before attempting operations. Upon detecting a dead instance, it clears the global reference, tears down associated sessions, cleans up virtual displays (X-vfb), and triggers a fresh launch on the next request, ensuring API resilience without manual intervention.

### Why use a subprocess launcher instead of running directly in the main process?

The **`launchServer()`** function in [`lib/launcher.js`](https://github.com/jo-inc/camofox-browser/blob/main/lib/launcher.js) isolates the HTTP server as a subprocess to prevent the main [`server.js`](https://github.com/jo-inc/camofox-browser/blob/main/server.js) from reading environment variables directly or spawning unintended child processes. This separation supports external supervisors (like systemd or containers) that manage the server lifecycle independently while keeping the browser automation logic clean and environment-agnostic.