Skip to content

Commit ec130d6

Browse files
committed
fixup! Use subprocess's timeout feature instead of reinventing the wheel
1 parent e2d689f commit ec130d6

2 files changed

Lines changed: 21 additions & 14 deletions

File tree

git/cmd.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def handle_process_output(
109109
stderr_handler: Union[None, Callable[[AnyStr], None], Callable[[List[AnyStr]], None]],
110110
finalizer: Union[None, Callable[[Union[Popen, "Git.AutoInterrupt"]], None]] = None,
111111
decode_streams: bool = True,
112-
kill_after_timeout: Optional[float, int] = None,
112+
kill_after_timeout: Optional[Union[float, int]] = None,
113113
) -> None:
114114
R"""Register for notifications to learn that process output is ready to read, and
115115
dispatch lines to the respective line handlers.
@@ -335,7 +335,7 @@ def __init__(
335335
self,
336336
proc: subprocess.Popen | None,
337337
args: Any,
338-
timeout: Optional[float, int] = None,
338+
timeout: Optional[Union[float, int]] = None,
339339
) -> None:
340340
self.proc = proc
341341
self.args = args
@@ -370,10 +370,15 @@ def _terminate(self) -> None:
370370
# Try to kill it.
371371
try:
372372
proc.terminate()
373-
status = proc.wait(timeout=self.timeout) # Ensure the process goes away.
373+
status = proc.wait(timeout=self.timeout) # Gracefully wait for the process to terminate.
374374

375375
self.status = self._status_code_if_terminate or status
376-
except (OSError, AttributeError, subprocess.TimeoutExpired) as ex:
376+
except subprocess.TimeoutExpired:
377+
_logger.warning("Process did not complete successfully in %s seconds; will forcefully kill", exc_info=True)
378+
proc.kill()
379+
status = proc.wait() # Ensure the process goes away by blocking with `timeout=None`.
380+
self.status = self._status_code_if_terminate or status
381+
except (OSError, AttributeError) as ex:
377382
# On interpreter shutdown (notably on Windows), parts of the stdlib used by
378383
# subprocess can already be torn down (e.g. `subprocess._winapi` becomes None),
379384
# which can cause AttributeError during terminate(). In that case, we prefer
@@ -389,7 +394,7 @@ def __getattr__(self, attr: str) -> Any:
389394
return getattr(self.proc, attr)
390395

391396
# TODO: Bad choice to mimic `proc.wait()` but with different args.
392-
def wait(self, stderr: Union[None, str, bytes] = b"") -> int:
397+
def wait(self, stderr: Optional[Union[str, bytes]] = b"", timeout: Optional[Union[int, float]] = None) -> int:
393398
"""Wait for the process and return its status code.
394399
395400
:param stderr:
@@ -406,7 +411,8 @@ def wait(self, stderr: Union[None, str, bytes] = b"") -> int:
406411
stderr_b = force_bytes(data=stderr, encoding="utf-8")
407412
status: Union[int, None]
408413
if self.proc is not None:
409-
status = self.proc.wait(timeout=self.timeout)
414+
timeout = self.timeout if timeout is None else timeout
415+
status = self.proc.wait(timeout=timeout)
410416
p_stderr = self.proc.stderr
411417
else: # Assume the underlying proc was killed earlier or never existed.
412418
status = self.status
@@ -1112,7 +1118,7 @@ def execute(
11121118
as_process: bool = False,
11131119
output_stream: Union[None, BinaryIO] = None,
11141120
stdout_as_string: bool = True,
1115-
kill_after_timeout: Optional[float, int] = None,
1121+
kill_after_timeout: Optional[Union[float, int]] = None,
11161122
with_stdout: bool = True,
11171123
universal_newlines: bool = False,
11181124
shell: Union[None, bool] = None,
@@ -1325,8 +1331,9 @@ def execute(
13251331
"error: process killed because it timed out. kill_after_timeout=%s seconds",
13261332
kill_after_timeout,
13271333
)
1328-
proc.terminate()
1329-
stdout_value, stderr_value = proc.communicate()
1334+
with contextlib.suppress(subprocess.TimeoutExpired):
1335+
proc.kill()
1336+
stdout_value, stderr_value = proc.communicate(timeout=0.1)
13301337
if with_exceptions:
13311338
raise GitCommandError(redacted_command, proc.returncode, stderr_value, stdout_value) from err
13321339
finally:

git/remote.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ def _get_fetch_info_from_stderr(
875875
self,
876876
proc: "Git.AutoInterrupt",
877877
progress: Union[Callable[..., Any], RemoteProgress, None],
878-
kill_after_timeout: Optional[float, int] = None,
878+
kill_after_timeout: Optional[Union[float, int]] = None,
879879
) -> IterableList["FetchInfo"]:
880880
progress = to_progress_instance(progress)
881881

@@ -946,7 +946,7 @@ def _get_push_info(
946946
self,
947947
proc: "Git.AutoInterrupt",
948948
progress: Union[Callable[..., Any], RemoteProgress, None],
949-
kill_after_timeout: Optional[float, int] = None,
949+
kill_after_timeout: Optional[Union[float, int]] = None,
950950
) -> PushInfoList:
951951
progress = to_progress_instance(progress)
952952

@@ -1004,7 +1004,7 @@ def fetch(
10041004
refspec: Union[str, List[str], None] = None,
10051005
progress: Union[RemoteProgress, None, "UpdateProgress"] = None,
10061006
verbose: bool = True,
1007-
kill_after_timeout: Optional[float, int] = None,
1007+
kill_after_timeout: Optional[Union[float, int]] = None,
10081008
allow_unsafe_protocols: bool = False,
10091009
allow_unsafe_options: bool = False,
10101010
**kwargs: Any,
@@ -1084,7 +1084,7 @@ def pull(
10841084
self,
10851085
refspec: Union[str, List[str], None] = None,
10861086
progress: Union[RemoteProgress, "UpdateProgress", None] = None,
1087-
kill_after_timeout: Optional[float, int] = None,
1087+
kill_after_timeout: Optional[Union[float, int]] = None,
10881088
allow_unsafe_protocols: bool = False,
10891089
allow_unsafe_options: bool = False,
10901090
**kwargs: Any,
@@ -1138,7 +1138,7 @@ def push(
11381138
self,
11391139
refspec: Union[str, List[str], None] = None,
11401140
progress: Union[RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None] = None,
1141-
kill_after_timeout: Optional[float, int] = None,
1141+
kill_after_timeout: Optional[Union[float, int]] = None,
11421142
allow_unsafe_protocols: bool = False,
11431143
allow_unsafe_options: bool = False,
11441144
**kwargs: Any,

0 commit comments

Comments
 (0)