WP-02: Persistent Browser Instance #89

Closed
opened 2026-03-13 04:36:57 +00:00 by cal · 1 comment
Owner

Description

Replace per-request Chromium launch/teardown with a persistent browser that lives for the lifetime of the API process. Eliminates ~1.0-1.5s spawn overhead per render.

Repo: database
Phase: 0 (Render Pipeline Optimization)
Dependencies: None (can run in parallel with WP-01)
Complexity: M

Current State

  • app/routers_v2/players.py lines 801-826: async with async_playwright() as p: block creates and destroys a browser per request
  • No browser reuse, no connection pooling

Implementation

  1. Add module-level _browser and _playwright globals to players.py
  2. Implement get_browser() — lazy-init with is_connected() auto-reconnect
  3. Implement shutdown_browser() — clean teardown for API shutdown
  4. Replace the async with async_playwright() block with page-per-request pattern:
    browser = await get_browser()
    page = await browser.new_page(viewport={"width": 1280, "height": 720})
    try:
        await page.set_content(html_string)
        await page.screenshot(path=file_path, type="png", clip={...})
    finally:
        await page.close()
    
  5. Ensure page is always closed in finally block to prevent memory leaks

Files

  • Modify: database/app/routers_v2/players.py — persistent browser, page-per-request

Tests

  • Unit: get_browser() returns a connected browser
  • Unit: get_browser() returns same instance on second call
  • Unit: get_browser() relaunches if browser disconnected
  • Integration: render 10 cards sequentially, no browser leaks (page count returns to 0 between renders)
  • Integration: concurrent renders (4 simultaneous requests) complete without errors
  • Integration: shutdown_browser() cleanly closes browser and playwright

Acceptance Criteria

  1. Only 1 Chromium process running regardless of render count
  2. Page count returns to 0 between renders (no leaks)
  3. Auto-reconnect works if browser crashes
  4. Per-card render time drops to ~1.0-1.5s (without font optimization)

Plan reference: docs/prd-evolution/PHASE0_PROJECT_PLAN.md WP-02

## Description Replace per-request Chromium launch/teardown with a persistent browser that lives for the lifetime of the API process. Eliminates ~1.0-1.5s spawn overhead per render. **Repo:** `database` **Phase:** 0 (Render Pipeline Optimization) **Dependencies:** None (can run in parallel with WP-01) **Complexity:** M ## Current State - `app/routers_v2/players.py` lines 801-826: `async with async_playwright() as p:` block creates and destroys a browser per request - No browser reuse, no connection pooling ## Implementation 1. Add module-level `_browser` and `_playwright` globals to `players.py` 2. Implement `get_browser()` — lazy-init with `is_connected()` auto-reconnect 3. Implement `shutdown_browser()` — clean teardown for API shutdown 4. Replace the `async with async_playwright()` block with page-per-request pattern: ```python browser = await get_browser() page = await browser.new_page(viewport={"width": 1280, "height": 720}) try: await page.set_content(html_string) await page.screenshot(path=file_path, type="png", clip={...}) finally: await page.close() ``` 5. Ensure page is always closed in `finally` block to prevent memory leaks ## Files - **Modify:** `database/app/routers_v2/players.py` — persistent browser, page-per-request ## Tests - [ ] Unit: `get_browser()` returns a connected browser - [ ] Unit: `get_browser()` returns same instance on second call - [ ] Unit: `get_browser()` relaunches if browser disconnected - [ ] Integration: render 10 cards sequentially, no browser leaks (page count returns to 0 between renders) - [ ] Integration: concurrent renders (4 simultaneous requests) complete without errors - [ ] Integration: `shutdown_browser()` cleanly closes browser and playwright ## Acceptance Criteria 1. Only 1 Chromium process running regardless of render count 2. Page count returns to 0 between renders (no leaks) 3. Auto-reconnect works if browser crashes 4. Per-card render time drops to ~1.0-1.5s (without font optimization) **Plan reference:** `docs/prd-evolution/PHASE0_PROJECT_PLAN.md` WP-02
cal added this to the Card Evolution Phase 0 — Render Pipeline Optimization milestone 2026-03-13 04:37:36 +00:00
cal added the
evolution
phase-0
labels 2026-03-13 04:37:41 +00:00
Claude added the
ai-working
label 2026-03-13 06:31:27 +00:00
Collaborator

PR opened: #97

Approach: Added get_browser() (lazy-init with is_connected() auto-reconnect) and shutdown_browser() to players.py as module-level globals. Replaced the async with async_playwright() per-request block with a page-per-request pattern — browser persists, each render gets a fresh page closed in a finally block. shutdown_browser() is wired into a FastAPI lifespan context manager in main.py for clean teardown on API shutdown.

PR opened: https://git.manticorum.com/cal/paper-dynasty-database/pulls/97 **Approach:** Added `get_browser()` (lazy-init with `is_connected()` auto-reconnect) and `shutdown_browser()` to `players.py` as module-level globals. Replaced the `async with async_playwright()` per-request block with a page-per-request pattern — browser persists, each render gets a fresh page closed in a `finally` block. `shutdown_browser()` is wired into a FastAPI `lifespan` context manager in `main.py` for clean teardown on API shutdown.
Claude added
ai-pr-opened
and removed
ai-working
labels 2026-03-13 06:35:51 +00:00
cal closed this issue 2026-03-16 16:21:31 +00:00
Sign in to join this conversation.
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: cal/paper-dynasty-database#89
No description provided.