Skip to content

Commit 18bb1d9

Browse files
authored
[py] terminate driver service process when start() fails to connect (#17651)
* [py] terminate driver service process when start() fails to connect * [py] guard service start cleanup against interrupts and cleanup errors * [py] add timeout to service remote shutdown request
1 parent b3164ef commit 18bb1d9

2 files changed

Lines changed: 63 additions & 11 deletions

File tree

py/selenium/webdriver/common/service.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,22 @@ def start(self) -> None:
108108
self._start_process(self._path)
109109

110110
count = 0
111-
while True:
112-
self.assert_process_still_running()
113-
if self.is_connectable():
114-
break
115-
# sleep increasing: 0.01, 0.06, 0.11, 0.16, 0.21, 0.26, 0.31, 0.36, 0.41, 0.46, 0.5
116-
sleep(min(0.01 + 0.05 * count, 0.5))
117-
count += 1
118-
if count == 70:
119-
raise WebDriverException(f"Can not connect to the Service {self._path}")
111+
try:
112+
while True:
113+
self.assert_process_still_running()
114+
if self.is_connectable():
115+
break
116+
# sleep increasing: 0.01, 0.06, 0.11, 0.16, 0.21, 0.26, 0.31, 0.36, 0.41, 0.46, 0.5
117+
sleep(min(0.01 + 0.05 * count, 0.5))
118+
count += 1
119+
if count == 70:
120+
raise WebDriverException(f"Can not connect to the Service {self._path}")
121+
except BaseException:
122+
try:
123+
self.stop()
124+
except Exception:
125+
logger.error("Error stopping service after a failed start.", exc_info=True)
126+
raise
120127

121128
def assert_process_still_running(self) -> None:
122129
"""Check if the underlying process is still running."""
@@ -137,8 +144,8 @@ def is_connectable(self) -> bool:
137144
def send_remote_shutdown_command(self) -> None:
138145
"""Dispatch an HTTP request to the shutdown endpoint to stop the service."""
139146
try:
140-
request.urlopen(f"{self.service_url}/shutdown")
141-
except URLError:
147+
request.urlopen(f"{self.service_url}/shutdown", timeout=10)
148+
except (URLError, TimeoutError):
142149
return
143150

144151
for _ in range(30):
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import sys
19+
20+
import pytest
21+
22+
from selenium.common.exceptions import WebDriverException
23+
from selenium.webdriver.common import utils
24+
from selenium.webdriver.common.service import Service
25+
26+
27+
class _UnreachableService(Service):
28+
"""A driver process that launches successfully but never serves /status."""
29+
30+
def command_line_args(self):
31+
return ["-c", "import time; time.sleep(30)"]
32+
33+
34+
def test_start_terminates_process_when_never_connectable(monkeypatch):
35+
monkeypatch.setattr("selenium.webdriver.common.service.sleep", lambda _: None)
36+
37+
service = _UnreachableService(executable_path=sys.executable, port=utils.free_port())
38+
39+
try:
40+
with pytest.raises(WebDriverException):
41+
service.start()
42+
43+
assert service.process.poll() is not None
44+
finally:
45+
service.stop()

0 commit comments

Comments
 (0)