-
-
Notifications
You must be signed in to change notification settings - Fork 937
Include 'timeout' parameter in Git execute #354
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
cd4caf1
9e1c90e
7cc0e6c
4faf5cd
d06e76b
dbbcaf7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
import mmap | ||
|
||
from contextlib import contextmanager | ||
from signal import SIGKILL | ||
from subprocess import ( | ||
call, | ||
Popen, | ||
|
@@ -41,7 +42,7 @@ | |
|
||
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', | ||
'with_exceptions', 'as_process', 'stdout_as_string', | ||
'output_stream', 'with_stdout') | ||
'output_stream', 'with_stdout', 'timeout') | ||
|
||
log = logging.getLogger('git.cmd') | ||
log.addHandler(logging.NullHandler()) | ||
|
@@ -475,6 +476,7 @@ def execute(self, command, | |
as_process=False, | ||
output_stream=None, | ||
stdout_as_string=True, | ||
timeout=None, | ||
with_stdout=True, | ||
**subprocess_kwargs | ||
): | ||
|
@@ -531,6 +533,12 @@ def execute(self, command, | |
|
||
:param with_stdout: If True, default True, we open stdout on the created process | ||
|
||
:param timeout: | ||
To specify a timeout in seconds for the git command, after which the process | ||
should be killed. This will have no effect if as_process is set to True. It is | ||
set to None by default and will let the process run until the timeout is | ||
explicitly specified. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would love to see that this feature is not supported on windows. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be good to make clear that |
||
|
||
:return: | ||
* str(output) if extended_output = False (Default) | ||
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True | ||
|
@@ -568,6 +576,8 @@ def execute(self, command, | |
|
||
if sys.platform == 'win32': | ||
cmd_not_found_exception = WindowsError | ||
if timeout: | ||
raise GitCommandError('"timeout" feature is not supported on Windows.') | ||
else: | ||
if sys.version_info[0] > 2: | ||
cmd_not_found_exception = FileNotFoundError # NOQA # this is defined, but flake8 doesn't know | ||
|
@@ -592,13 +602,45 @@ def execute(self, command, | |
if as_process: | ||
return self.AutoInterrupt(proc, command) | ||
|
||
def _kill_process(pid): | ||
""" Callback method to kill a process. """ | ||
p = Popen(['ps', '--ppid', str(pid)], stdout=PIPE) | ||
child_pids = [] | ||
for line in p.stdout: | ||
if len(line.split()) > 0: | ||
local_pid = (line.split())[0] | ||
if local_pid.isdigit(): | ||
child_pids.append(int(local_pid)) | ||
try: | ||
os.kill(pid, SIGKILL) | ||
for child_pid in child_pids: | ||
os.kill(child_pid, SIGKILL) | ||
kill_check.set() # tell the main routine that the process was killed | ||
except OSError: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If for child_pid in child_pids:
try:
os.kill(child_pid, SIGKILL)
except OSError:
pass |
||
# It is possible that the process gets completed in the duration after timeout | ||
# happens and before we try to kill the process. | ||
pass | ||
return | ||
# end | ||
|
||
if timeout: | ||
kill_check = threading.Event() | ||
watchdog = threading.Timer(timeout, _kill_process, args=(proc.pid, )) | ||
|
||
# Wait for the process to return | ||
status = 0 | ||
stdout_value = b'' | ||
stderr_value = b'' | ||
try: | ||
if output_stream is None: | ||
if timeout: | ||
watchdog.start() | ||
stdout_value, stderr_value = proc.communicate() | ||
if timeout: | ||
watchdog.cancel() | ||
if kill_check.isSet(): | ||
stderr_value = 'Timeout: the command "%s" did not complete in %d ' \ | ||
'secs.' % (" ".join(command), timeout) | ||
# strip trailing "\n" | ||
if stdout_value.endswith(b"\n"): | ||
stdout_value = stdout_value[:-1] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about renaming the parameter to something even more descriptive, like
kill_after_timeout
to be very clear about theSIGKILL
nature of the operation.