From 0267db4722a7425d87150404ccaeeadfbd2792cd Mon Sep 17 00:00:00 2001 From: Dmitry Vorotilin Date: Wed, 2 Sep 2026 22:05:52 +0300 Subject: [PATCH] change(options): stop tying :protocol_timeout to :timeout `:protocol_timeout` bounds internal CDP bookkeeping, `Target.createTarget`, `Target.attachToTarget` and friends, which resolve in milliseconds. It defaulted to `:timeout`, so anyone lowering that to keep page waits short also gave the browser's own bookkeeping the same tiny budget, and on a loaded machine page creation started raising `Ferrum::TimeoutError`. It now defaults to 30 seconds, still overridable per browser or through `FERRUM_PROTOCOL_TIMEOUT`. Running out of it means the machine is struggling rather than the browser being gone, which is detected on its own. Refs #470 --- CHANGELOG.md | 3 +++ docs/2-customization.md | 7 ++++++- lib/ferrum/browser.rb | 6 ++++-- lib/ferrum/browser/options.rb | 2 +- spec/browser_spec.rb | 13 +++++++++++++ 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f477cd3..c1fd9284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ mobile device's viewport and touch support together. [#94] ### Changed +- `:protocol_timeout` defaults to 30 seconds instead of following `:timeout`. Internal CDP calls resolve in + milliseconds, so the old default made `timeout: 1` shorten browser bookkeeping too and turned a loaded machine + into `Ferrum::TimeoutError` during page creation [#470] - `Ferrum::Page::Stream#stream` now closes the CDP stream handle (`IO.close`) once it's been fully read, so streams opened for `Ferrum::Browser#pdf` and `Ferrum::Page::Tracing#record` no longer keep their backing storage alive in the browser; a failed `IO.close` is raised to the caller. diff --git a/docs/2-customization.md b/docs/2-customization.md index d9366afb..fe7b489b 100644 --- a/docs/2-customization.md +++ b/docs/2-customization.md @@ -28,7 +28,12 @@ Ferrum::Browser.new(options) * `:slowmo` (Integer | Float) - Set a delay in seconds to wait before sending command. Useful companion of headless option, so that you have time to see changes. * `:timeout` (Numeric) - The number of seconds we'll wait for a response when - communicating with browser. Default is 5. + communicating with browser: navigations, JS evaluation, DOM queries, + dispatching input, etc. Default is 5. + * `:protocol_timeout` (Numeric) - The number of seconds we'll wait for an + internal CDP bookkeeping call to respond, e.g. `Target.createTarget`. + They normally resolve in milliseconds, so this only runs out when the + machine is heavily loaded. Default is 30. * `:js_errors` (Boolean) - When true, JavaScript errors get re-raised in Ruby. * `:pending_connection_errors` (Boolean) - Raise `PendingConnectionsError` when main frame is still waiting for slow responses and timeout is reached. Default is false. diff --git a/lib/ferrum/browser.rb b/lib/ferrum/browser.rb index 4cea1153..ed00983f 100644 --- a/lib/ferrum/browser.rb +++ b/lib/ferrum/browser.rb @@ -89,10 +89,12 @@ class Browser # The number of seconds we'll wait for a response when communicating # with the browser: navigations, JS evaluation, DOM queries, dispatching input, etc. # - # @option options [Numeric] :protocol_timeout (5) + # @option options [Numeric] :protocol_timeout (30) # The number of seconds we'll wait for an individual internal CDP # bookkeeping call to respond, e.g. `Target.createTarget`, - # `Target.attachToTarget`. These normally resolve in milliseconds. + # `Target.attachToTarget`. These normally resolve in milliseconds, the + # budget is generous because exceeding it means the machine is loaded, + # not that the browser is gone, that case is detected on its own. # # @option options [Boolean] :js_errors # When true, JavaScript errors get re-raised in Ruby. diff --git a/lib/ferrum/browser/options.rb b/lib/ferrum/browser/options.rb index 0a6cbec0..77b1a4a1 100644 --- a/lib/ferrum/browser/options.rb +++ b/lib/ferrum/browser/options.rb @@ -14,7 +14,7 @@ class Options WINDOW_SIZE = [1024, 768].freeze BASE_URL_SCHEMA = %w[http https].freeze DEFAULT_TIMEOUT = ENV.fetch("FERRUM_DEFAULT_TIMEOUT", 5).to_i - DEFAULT_PROTOCOL_TIMEOUT = ENV.fetch("FERRUM_PROTOCOL_TIMEOUT", DEFAULT_TIMEOUT).to_i + DEFAULT_PROTOCOL_TIMEOUT = ENV.fetch("FERRUM_PROTOCOL_TIMEOUT", 30).to_i PROCESS_TIMEOUT = ENV.fetch("FERRUM_PROCESS_TIMEOUT", 10).to_i DEBUG_MODE = !ENV.fetch("FERRUM_DEBUG", nil).nil? diff --git a/spec/browser_spec.rb b/spec/browser_spec.rb index dfea13fa..f63b5268 100644 --- a/spec/browser_spec.rb +++ b/spec/browser_spec.rb @@ -63,6 +63,19 @@ browser&.quit end + it "supports :protocol_timeout argument" do + browser = Ferrum::Browser.new(base_url: base_url, timeout: 1) + + expect(browser.timeout).to eq(1) + expect(browser.protocol_timeout).to eq(30) + + browser.protocol_timeout = 2 + + expect(browser.protocol_timeout).to eq(2) + ensure + browser&.quit + end + it "supports :process_timeout argument" do path = "#{PROJECT_ROOT}/spec/support/no_chrome"