Skip to content

Commit cd4caf1

Browse files
OswinNathanialdpursehouse
authored andcommitted
Include 'timeout' parameter in Git execute
This feature enables to set a timeout while executing a git command. After this timeout is over, the process will be killed. If not explicitly specified, the default functionality will not be affected. Change-Id: I2dd5f0de7cb1f5f1b4253dd7ce92d23551d5f9a7
1 parent 51f79ff commit cd4caf1

File tree

1 file changed

+39
-1
lines changed

1 file changed

+39
-1
lines changed

git/cmd.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import mmap
1515

1616
from contextlib import contextmanager
17+
from signal import SIGKILL
1718
from subprocess import (
1819
call,
1920
Popen,
@@ -41,7 +42,7 @@
4142

4243
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
4344
'with_exceptions', 'as_process', 'stdout_as_string',
44-
'output_stream', 'with_stdout')
45+
'output_stream', 'with_stdout', 'timeout')
4546

4647
log = logging.getLogger('git.cmd')
4748
log.addHandler(logging.NullHandler())
@@ -475,6 +476,7 @@ def execute(self, command,
475476
as_process=False,
476477
output_stream=None,
477478
stdout_as_string=True,
479+
timeout=None,
478480
with_stdout=True,
479481
**subprocess_kwargs
480482
):
@@ -531,6 +533,12 @@ def execute(self, command,
531533
532534
:param with_stdout: If True, default True, we open stdout on the created process
533535
536+
:param timeout:
537+
To specify a timeout in seconds for the git command, after which the process
538+
should be killed. This will have no effect if as_process is set to True. It is
539+
set to None by default and will let the process run until the timeout is
540+
explicitly specified.
541+
534542
:return:
535543
* str(output) if extended_output = False (Default)
536544
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
@@ -592,13 +600,43 @@ def execute(self, command,
592600
if as_process:
593601
return self.AutoInterrupt(proc, command)
594602

603+
kill_check = threading.Event()
604+
605+
def _kill_process(pid):
606+
""" Callback method to kill a process. """
607+
p = Popen(['ps', '--ppid', str(pid)], stdout=PIPE)
608+
child_pids = []
609+
for line in p.stdout:
610+
if len(line.split()) > 0:
611+
local_pid = (line.split())[0]
612+
if local_pid.isdigit():
613+
child_pids.append(int(local_pid))
614+
try:
615+
os.kill(pid, SIGKILL)
616+
for child_pid in child_pids:
617+
os.kill(child_pid, SIGKILL)
618+
kill_check.set() # tell the main routine that the process was killed
619+
except OSError:
620+
# It is possible that the process gets completed in the duration after timeout
621+
# happens and before we try to kill the process.
622+
pass
623+
return
624+
# end
625+
626+
watchdog = threading.Timer(timeout, _kill_process, args=(proc.pid, ))
627+
595628
# Wait for the process to return
596629
status = 0
597630
stdout_value = b''
598631
stderr_value = b''
599632
try:
600633
if output_stream is None:
634+
watchdog.start()
601635
stdout_value, stderr_value = proc.communicate()
636+
watchdog.cancel()
637+
if kill_check.isSet():
638+
stderr_value = 'Timeout: the command "%s" did not complete in %d ' \
639+
'secs.' % (" ".join(command), timeout)
602640
# strip trailing "\n"
603641
if stdout_value.endswith(b"\n"):
604642
stdout_value = stdout_value[:-1]

0 commit comments

Comments
 (0)