Skip to content

Simplify browser management with single thread-safe instance#63

Merged
excid3 merged 1 commit intoexcid3:mainfrom
brandoncc:simplify-browser-management
Jul 17, 2025
Merged

Simplify browser management with single thread-safe instance#63
excid3 merged 1 commit intoexcid3:mainfrom
brandoncc:simplify-browser-management

Conversation

@brandoncc
Copy link
Contributor

@brandoncc brandoncc commented Jul 17, 2025

Summary

  • Replaces complex multiple browser registry with single thread-safe browser instance
  • Ensures only one Chrome process per Ruby process
  • Maintains thread safety for background jobs and concurrent requests

Breaking Changes

  • Removed: add_browser method and browsers hash
  • Removed: browser: parameter from render_pdf/render_screenshot methods

New API

  • Added: FerrumPdf.browser = browser_instance setter for custom browsers
  • Added: FerrumPdf.with_browser method for thread-safe browser access
  • Enhanced: Auto-creation of browser from config when none is set
  • Enhanced: Safe browser shutdown with FerrumPdf.browser = nil

Browser Configuration Discussion

This PR introduces two ways to configure the browser:

  1. Global config (existing): FerrumPdf.configure { |c| c.window_size = [800, 600] }
  2. Direct instance (new): FerrumPdf.browser = Ferrum::Browser.new(window_size: [800, 600])

Is it worth maintaining two paths for configuring the browser? Since the simplest use case doesn't have to write a config at all and just uses the library's default config, it might be. It is a good time to consider removing .configure, though.

Benefits

  • ✅ Guarantees single Chrome instance per Ruby process
  • ✅ Thread-safe for background jobs and concurrent requests
  • ✅ Simplified codebase (~25 lines of complexity removed)
  • ✅ Users retain full control over browser configuration
  • ✅ Maintains backward compatibility for configure block (for now)

Testing

  • All existing integration tests pass
  • Added comprehensive unit tests for new browser management
  • Tests cover auto-creation, replacement, thread safety, and configuration

🤖 Generated with Claude Code

@brandoncc brandoncc force-pushed the simplify-browser-management branch 3 times, most recently from 2cf950a to 97f8b28 Compare July 17, 2025 04:09
Replace multiple browser registry system with single thread-safe browser
instance to ensure only one Chrome process per Ruby process.

**Breaking Changes:**
- Removed `add_browser` method and `browsers` hash
- Removed `browser:` parameter from render methods

**New Features:**
- Added `FerrumPdf.browser = browser_instance` setter
- Added `FerrumPdf.with_browser` method for thread-safe browser access
- Auto-creation of browser from config when none is set
- Thread-safe mutex protection for concurrent access

**Benefits:**
- Guarantees single Chrome instance per Ruby process
- Thread-safe for background jobs and concurrent requests
- Simplified codebase with reduced complexity
- Users can still provide custom browser instances
@brandoncc brandoncc force-pushed the simplify-browser-management branch from 97f8b28 to b5a5648 Compare July 17, 2025 04:11
# This automatically applies HTML preprocessing if `html:` is present
#
def load_page(url: nil, html: nil, base_url: nil, authorize: nil, wait_for_idle_options: nil, browser: :default, retries: 1)
def load_page(url: nil, html: nil, base_url: nil, authorize: nil, wait_for_idle_options: nil, retries: 1)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should keep the browser: option, just in case someone needs to provide a separate browser instance.

That wouldn't need to quit the existing browser like reassigning the default browser would.

Comment on lines +43 to +48
def with_browser
browser_mutex.synchronize do
@@browser ||= Ferrum::Browser.new(config)
@@browser.restart unless @@browser.client.present?
yield @@browser
end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def with_browser
browser_mutex.synchronize do
@@browser ||= Ferrum::Browser.new(config)
@@browser.restart unless @@browser.client.present?
yield @@browser
end
def with_browser(browser=nil)
if browser.present?
yield browser
else
browser_mutex.synchronize do
@@browser ||= Ferrum::Browser.new(config)
@@browser.restart unless @@browser.client.present?
yield @@browser
end
end

If we want to support the browser: nil option for render_pdf to let users pass in their own separate browser, we could do something like this.

@excid3
Copy link
Owner

excid3 commented Jul 17, 2025

I think we should probably keep config so that you can define your own browser config without having to start a browser immediately.

@excid3 excid3 merged commit b5a5648 into excid3:main Jul 17, 2025
2 checks passed
@brandoncc brandoncc deleted the simplify-browser-management branch July 17, 2025 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants