From 9b8cc1fdd69988467e66dc1415ff704c59833c62 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Sat, 6 Jun 2026 09:51:04 -0500 Subject: [PATCH 1/3] [py] terminate driver service process when start() fails to connect --- py/selenium/webdriver/common/service.py | 22 ++++++---- .../webdriver/common/service_tests.py | 42 +++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 py/test/unit/selenium/webdriver/common/service_tests.py diff --git a/py/selenium/webdriver/common/service.py b/py/selenium/webdriver/common/service.py index 41d76ffac2113..24c1a4d0efeb5 100644 --- a/py/selenium/webdriver/common/service.py +++ b/py/selenium/webdriver/common/service.py @@ -108,15 +108,19 @@ def start(self) -> None: self._start_process(self._path) count = 0 - while True: - self.assert_process_still_running() - if self.is_connectable(): - break - # sleep increasing: 0.01, 0.06, 0.11, 0.16, 0.21, 0.26, 0.31, 0.36, 0.41, 0.46, 0.5 - sleep(min(0.01 + 0.05 * count, 0.5)) - count += 1 - if count == 70: - raise WebDriverException(f"Can not connect to the Service {self._path}") + try: + while True: + self.assert_process_still_running() + if self.is_connectable(): + break + # sleep increasing: 0.01, 0.06, 0.11, 0.16, 0.21, 0.26, 0.31, 0.36, 0.41, 0.46, 0.5 + sleep(min(0.01 + 0.05 * count, 0.5)) + count += 1 + if count == 70: + raise WebDriverException(f"Can not connect to the Service {self._path}") + except Exception: + self.stop() + raise def assert_process_still_running(self) -> None: """Check if the underlying process is still running.""" diff --git a/py/test/unit/selenium/webdriver/common/service_tests.py b/py/test/unit/selenium/webdriver/common/service_tests.py new file mode 100644 index 0000000000000..cd5a8af344098 --- /dev/null +++ b/py/test/unit/selenium/webdriver/common/service_tests.py @@ -0,0 +1,42 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import sys + +import pytest + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils +from selenium.webdriver.common.service import Service + + +class _UnreachableService(Service): + """A driver process that launches successfully but never serves /status.""" + + def command_line_args(self): + return ["-c", "import time; time.sleep(30)"] + + +def test_start_terminates_process_when_never_connectable(monkeypatch): + monkeypatch.setattr("selenium.webdriver.common.service.sleep", lambda _: None) + + service = _UnreachableService(executable_path=sys.executable, port=utils.free_port()) + + with pytest.raises(WebDriverException): + service.start() + + assert service.process.poll() is not None From 6ad1b829e9f2f9478e3227278eb3a37509b8a479 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Sat, 6 Jun 2026 10:31:16 -0500 Subject: [PATCH 2/3] [py] guard service start cleanup against interrupts and cleanup errors --- py/selenium/webdriver/common/service.py | 7 +++++-- py/test/unit/selenium/webdriver/common/service_tests.py | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/py/selenium/webdriver/common/service.py b/py/selenium/webdriver/common/service.py index 24c1a4d0efeb5..4d410fb1dc268 100644 --- a/py/selenium/webdriver/common/service.py +++ b/py/selenium/webdriver/common/service.py @@ -118,8 +118,11 @@ def start(self) -> None: count += 1 if count == 70: raise WebDriverException(f"Can not connect to the Service {self._path}") - except Exception: - self.stop() + except BaseException: + try: + self.stop() + except Exception: + logger.error("Error stopping service after a failed start.", exc_info=True) raise def assert_process_still_running(self) -> None: diff --git a/py/test/unit/selenium/webdriver/common/service_tests.py b/py/test/unit/selenium/webdriver/common/service_tests.py index cd5a8af344098..f695b7462ea78 100644 --- a/py/test/unit/selenium/webdriver/common/service_tests.py +++ b/py/test/unit/selenium/webdriver/common/service_tests.py @@ -36,7 +36,10 @@ def test_start_terminates_process_when_never_connectable(monkeypatch): service = _UnreachableService(executable_path=sys.executable, port=utils.free_port()) - with pytest.raises(WebDriverException): - service.start() + try: + with pytest.raises(WebDriverException): + service.start() - assert service.process.poll() is not None + assert service.process.poll() is not None + finally: + service.stop() From 60fc0c7c41d213de8a3cdc0836f2b97558511213 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Sat, 6 Jun 2026 11:43:24 -0500 Subject: [PATCH 3/3] [py] add timeout to service remote shutdown request --- py/selenium/webdriver/common/service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/selenium/webdriver/common/service.py b/py/selenium/webdriver/common/service.py index 4d410fb1dc268..aef101ea0f3f8 100644 --- a/py/selenium/webdriver/common/service.py +++ b/py/selenium/webdriver/common/service.py @@ -144,8 +144,8 @@ def is_connectable(self) -> bool: def send_remote_shutdown_command(self) -> None: """Dispatch an HTTP request to the shutdown endpoint to stop the service.""" try: - request.urlopen(f"{self.service_url}/shutdown") - except URLError: + request.urlopen(f"{self.service_url}/shutdown", timeout=10) + except (URLError, TimeoutError): return for _ in range(30):