diff --git a/py/selenium/webdriver/common/service.py b/py/selenium/webdriver/common/service.py index 41d76ffac2113..aef101ea0f3f8 100644 --- a/py/selenium/webdriver/common/service.py +++ b/py/selenium/webdriver/common/service.py @@ -108,15 +108,22 @@ 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 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: """Check if the underlying process is still running.""" @@ -137,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): 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..f695b7462ea78 --- /dev/null +++ b/py/test/unit/selenium/webdriver/common/service_tests.py @@ -0,0 +1,45 @@ +# 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()) + + try: + with pytest.raises(WebDriverException): + service.start() + + assert service.process.poll() is not None + finally: + service.stop()