diff options
author | Dmitry Kopylov <kopylovd@gmail.com> | 2022-02-10 16:48:18 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:48:18 +0300 |
commit | 7230275728d34873cba1ba78bb68669b0c5faa31 (patch) | |
tree | b222e5ac2e2e98872661c51ccceee5da0d291e13 /library/python/testing/yatest_common/yatest/common/process.py | |
parent | b2f5101486cc0de2e979c8ba9ada2109785bf5fd (diff) | |
download | ydb-7230275728d34873cba1ba78bb68669b0c5faa31.tar.gz |
Restoring authorship annotation for Dmitry Kopylov <kopylovd@gmail.com>. Commit 2 of 2.
Diffstat (limited to 'library/python/testing/yatest_common/yatest/common/process.py')
-rw-r--r-- | library/python/testing/yatest_common/yatest/common/process.py | 576 |
1 files changed, 288 insertions, 288 deletions
diff --git a/library/python/testing/yatest_common/yatest/common/process.py b/library/python/testing/yatest_common/yatest/common/process.py index 672ebe0823..a8bcc21f51 100644 --- a/library/python/testing/yatest_common/yatest/common/process.py +++ b/library/python/testing/yatest_common/yatest/common/process.py @@ -1,16 +1,16 @@ # coding: utf-8 -import os +import os import re -import time -import signal +import time +import signal import shutil -import logging -import tempfile -import subprocess -import errno +import logging +import tempfile +import subprocess +import errno import distutils.version - + import six try: @@ -22,13 +22,13 @@ except ImportError: from . import runtime from . import path from . import environment - - + + MAX_OUT_LEN = 1000 * 1000 # 1 mb MAX_MESSAGE_LEN = 1500 SANITIZER_ERROR_PATTERN = br": ([A-Z][\w]+Sanitizer)" GLIBC_PATTERN = re.compile(r"\S+@GLIBC_([0-9.]+)") -yatest_logger = logging.getLogger("ya.test") +yatest_logger = logging.getLogger("ya.test") def truncate(s, size): @@ -40,45 +40,45 @@ def truncate(s, size): return (b'...' if isinstance(s, bytes) else '...') + s[-(size - 3):] -def get_command_name(command): +def get_command_name(command): return os.path.basename(command.split()[0] if isinstance(command, six.string_types) else command[0]) - - -class ExecutionError(Exception): - - def __init__(self, execution_result): + + +class ExecutionError(Exception): + + def __init__(self, execution_result): if not isinstance(execution_result.command, six.string_types): command = " ".join(str(arg) for arg in execution_result.command) else: command = execution_result.command - message = "Command '{command}' has failed with code {code}.\nErrors:\n{err}\n".format( + message = "Command '{command}' has failed with code {code}.\nErrors:\n{err}\n".format( command=command, - code=execution_result.exit_code, - err=_format_error(execution_result.std_err)) + code=execution_result.exit_code, + err=_format_error(execution_result.std_err)) if cores: if execution_result.backtrace: message += "Backtrace:\n[[rst]]{}[[bad]]\n".format(cores.colorize_backtrace(execution_result._backtrace)) else: message += "Backtrace is not available: module cores isn't available" - super(ExecutionError, self).__init__(message) + super(ExecutionError, self).__init__(message) + self.execution_result = execution_result + + +class TimeoutError(Exception): + pass + + +class ExecutionTimeoutError(TimeoutError): + def __init__(self, execution_result, *args, **kwargs): + super(ExecutionTimeoutError, self).__init__(args, kwargs) self.execution_result = execution_result - - -class TimeoutError(Exception): - pass - - -class ExecutionTimeoutError(TimeoutError): - def __init__(self, execution_result, *args, **kwargs): - super(ExecutionTimeoutError, self).__init__(args, kwargs) - self.execution_result = execution_result - - -class InvalidExecutionStateError(Exception): - pass - - + + +class InvalidExecutionStateError(Exception): + pass + + class SignalInterruptionError(Exception): def __init__(self, message=None): super(SignalInterruptionError, self).__init__(message) @@ -89,39 +89,39 @@ class InvalidCommandError(Exception): pass -class _Execution(object): - +class _Execution(object): + def __init__(self, command, process, out_file, err_file, process_progress_listener=None, cwd=None, collect_cores=True, check_sanitizer=True, started=0, user_stdout=False, user_stderr=False): - self._command = command - self._process = process - self._out_file = out_file - self._err_file = err_file - self._std_out = None - self._std_err = None + self._command = command + self._process = process + self._out_file = out_file + self._err_file = err_file + self._std_out = None + self._std_err = None self._elapsed = None self._start = time.time() - self._process_progress_listener = process_progress_listener + self._process_progress_listener = process_progress_listener self._cwd = cwd or os.getcwd() self._collect_cores = collect_cores self._backtrace = '' self._check_sanitizer = check_sanitizer - self._metrics = {} + self._metrics = {} self._started = started self._user_stdout = bool(user_stdout) self._user_stderr = bool(user_stderr) self._exit_code = None - if process_progress_listener: - process_progress_listener.open(command, process, out_file, err_file) - - @property - def running(self): - return self._process.poll() is None - - def kill(self): - if self.running: - self._save_outputs(False) - _kill_process_tree(self._process.pid) - self._clean_files() + if process_progress_listener: + process_progress_listener.open(command, process, out_file, err_file) + + @property + def running(self): + return self._process.poll() is None + + def kill(self): + if self.running: + self._save_outputs(False) + _kill_process_tree(self._process.pid) + self._clean_files() # DEVTOOLS-2347 yatest_logger.debug("Process status before wait_for: %s", self.running) try: @@ -130,64 +130,64 @@ class _Execution(object): yatest_logger.debug("Process status after wait_for: %s", self.running) yatest_logger.debug("Process %d info: %s", self._process.pid, _get_proc_tree_info([self._process.pid])) raise - else: - raise InvalidExecutionStateError("Cannot kill a stopped process") - + else: + raise InvalidExecutionStateError("Cannot kill a stopped process") + def terminate(self): if self.running: self._process.terminate() - @property - def process(self): - return self._process - - @property - def command(self): - return self._command - - @property + @property + def process(self): + return self._process + + @property + def command(self): + return self._command + + @property def returncode(self): return self.exit_code @property - def exit_code(self): + def exit_code(self): """ Deprecated, use returncode """ if self._exit_code is None: self._exit_code = self._process.returncode return self._exit_code - - @property + + @property def stdout(self): return self.std_out @property - def std_out(self): + def std_out(self): """ Deprecated, use stdout """ - if self._std_out is not None: - return self._std_out + if self._std_out is not None: + return self._std_out if self._process.stdout and not self._user_stdout: self._std_out = self._process.stdout.read() return self._std_out - - @property + + @property def stderr(self): return self.std_err @property - def std_err(self): + def std_err(self): """ Deprecated, use stderr """ - if self._std_err is not None: - return self._std_err + if self._std_err is not None: + return self._std_err if self._process.stderr and not self._user_stderr: self._std_err = self._process.stderr.read() return self._std_err - + @property def elapsed(self): return self._elapsed @@ -196,14 +196,14 @@ class _Execution(object): def backtrace(self): return self._backtrace - @property - def metrics(self): - return self._metrics - - def _save_outputs(self, clean_files=True): - if self._process_progress_listener: - self._process_progress_listener() - self._process_progress_listener.close() + @property + def metrics(self): + return self._metrics + + def _save_outputs(self, clean_files=True): + if self._process_progress_listener: + self._process_progress_listener() + self._process_progress_listener.close() if not self._user_stdout: if self._out_file is None: pass @@ -223,14 +223,14 @@ class _Execution(object): else: self._std_err = self._process.stderr.read() - if clean_files: - self._clean_files() - yatest_logger.debug("Command (pid %s) rc: %s", self._process.pid, self.exit_code) - yatest_logger.debug("Command (pid %s) elapsed time (sec): %s", self._process.pid, self.elapsed) - if self._metrics: + if clean_files: + self._clean_files() + yatest_logger.debug("Command (pid %s) rc: %s", self._process.pid, self.exit_code) + yatest_logger.debug("Command (pid %s) elapsed time (sec): %s", self._process.pid, self.elapsed) + if self._metrics: for key, value in six.iteritems(self._metrics): - yatest_logger.debug("Command (pid %s) %s: %s", self._process.pid, key, value) - + yatest_logger.debug("Command (pid %s) %s: %s", self._process.pid, key, value) + # Since this code is Python2/3 compatible, we don't know is _std_out/_std_err is real bytes or bytes-str. printable_std_out, err = _try_convert_bytes_to_string(self._std_out) if err: @@ -244,14 +244,14 @@ class _Execution(object): yatest_logger.debug("Command (pid %s) output:\n%s", self._process.pid, truncate(printable_std_out, MAX_OUT_LEN)) yatest_logger.debug("Command (pid %s) errors:\n%s", self._process.pid, truncate(printable_std_err, MAX_OUT_LEN)) - def _clean_files(self): + def _clean_files(self): if self._err_file and not self._user_stderr and self._err_file != subprocess.PIPE: self._err_file.close() self._err_file = None if self._out_file and not self._user_stdout and self._out_file != subprocess.PIPE: self._out_file.close() self._out_file = None - + def _recover_core(self): core_path = cores.recover_core_dump_file(self.command[0], self._cwd, self.process.pid) if core_path: @@ -260,7 +260,7 @@ class _Execution(object): if store_cores: new_core_path = path.get_unique_file_path(runtime.output_path(), "{}.{}.core".format(os.path.basename(self.command[0]), self._process.pid)) # Copy core dump file, because it may be overwritten - yatest_logger.debug("Coping core dump file from '%s' to the '%s'", core_path, new_core_path) + yatest_logger.debug("Coping core dump file from '%s' to the '%s'", core_path, new_core_path) shutil.copyfile(core_path, new_core_path) core_path = new_core_path @@ -281,92 +281,92 @@ class _Execution(object): else: runtime._register_core(os.path.basename(self.command[0]), None, None, bt_filename, pbt_filename) - def wait(self, check_exit_code=True, timeout=None, on_timeout=None): - def _wait(): + def wait(self, check_exit_code=True, timeout=None, on_timeout=None): + def _wait(): finished = None interrupted = False - try: - if hasattr(os, "wait4"): - try: + try: + if hasattr(os, "wait4"): + try: if hasattr(subprocess, "_eintr_retry_call"): pid, sts, rusage = subprocess._eintr_retry_call(os.wait4, self._process.pid, 0) else: # PEP 475 pid, sts, rusage = os.wait4(self._process.pid, 0) finished = time.time() - self._process._handle_exitstatus(sts) - for field in [ - "ru_idrss", - "ru_inblock", - "ru_isrss", - "ru_ixrss", - "ru_majflt", - "ru_maxrss", - "ru_minflt", - "ru_msgrcv", - "ru_msgsnd", - "ru_nivcsw", - "ru_nsignals", - "ru_nswap", - "ru_nvcsw", - "ru_oublock", - "ru_stime", - "ru_utime", - ]: - if hasattr(rusage, field): - self._metrics[field.replace("ru_", "")] = getattr(rusage, field) - except OSError as exc: - - if exc.errno == errno.ECHILD: - yatest_logger.debug("Process resource usage is not available as process finished before wait4 was called") - else: - raise + self._process._handle_exitstatus(sts) + for field in [ + "ru_idrss", + "ru_inblock", + "ru_isrss", + "ru_ixrss", + "ru_majflt", + "ru_maxrss", + "ru_minflt", + "ru_msgrcv", + "ru_msgsnd", + "ru_nivcsw", + "ru_nsignals", + "ru_nswap", + "ru_nvcsw", + "ru_oublock", + "ru_stime", + "ru_utime", + ]: + if hasattr(rusage, field): + self._metrics[field.replace("ru_", "")] = getattr(rusage, field) + except OSError as exc: + + if exc.errno == errno.ECHILD: + yatest_logger.debug("Process resource usage is not available as process finished before wait4 was called") + else: + raise except SignalInterruptionError: interrupted = True raise - finally: + finally: if not interrupted: self._process.wait() # this has to be here unconditionally, so that all process properties are set - + if not finished: finished = time.time() self._metrics["wtime"] = round(finished - self._started, 3) - try: + try: if timeout: process_is_finished = lambda: not self.running fail_message = "Command '%s' stopped by %d seconds timeout" % (self._command, timeout) - try: - wait_for(process_is_finished, timeout, fail_message, sleep_time=0.1, on_check_condition=self._process_progress_listener) - except TimeoutError as e: - if on_timeout: + try: + wait_for(process_is_finished, timeout, fail_message, sleep_time=0.1, on_check_condition=self._process_progress_listener) + except TimeoutError as e: + if on_timeout: yatest_logger.debug("Calling user specified on_timeout function") - try: - on_timeout(self, timeout) - except Exception: - yatest_logger.exception("Exception while calling on_timeout") - raise ExecutionTimeoutError(self, str(e)) + try: + on_timeout(self, timeout) + except Exception: + yatest_logger.exception("Exception while calling on_timeout") + raise ExecutionTimeoutError(self, str(e)) # Wait should be always called here, it finalizes internal states of its process and sets up return code - _wait() + _wait() except BaseException as e: - _kill_process_tree(self._process.pid) - _wait() - yatest_logger.debug("Command exception: %s", e) + _kill_process_tree(self._process.pid) + _wait() + yatest_logger.debug("Command exception: %s", e) raise - finally: + finally: self._elapsed = time.time() - self._start self._save_outputs() self.verify_no_coredumps() - + self._finalise(check_exit_code) def _finalise(self, check_exit_code): - # Set the signal (negative number) which caused the process to exit - if check_exit_code and self.exit_code != 0: - yatest_logger.error("Execution failed with exit code: %s\n\t,std_out:%s\n\tstd_err:%s\n", - self.exit_code, truncate(self.std_out, MAX_OUT_LEN), truncate(self.std_err, MAX_OUT_LEN)) - raise ExecutionError(self) - + # Set the signal (negative number) which caused the process to exit + if check_exit_code and self.exit_code != 0: + yatest_logger.error("Execution failed with exit code: %s\n\t,std_out:%s\n\tstd_err:%s\n", + self.exit_code, truncate(self.std_out, MAX_OUT_LEN), truncate(self.std_err, MAX_OUT_LEN)) + raise ExecutionError(self) + # Don't search for sanitize errors if stderr was redirected self.verify_sanitize_errors() @@ -394,11 +394,11 @@ class _Execution(object): if match: yatest_logger.error("%s sanitizer found errors:\n\tstd_err:%s\n", match.group(1), truncate(self.std_err, MAX_OUT_LEN)) raise ExecutionError(self) - else: - yatest_logger.debug("No sanitizer errors found") + else: + yatest_logger.debug("No sanitizer errors found") else: - yatest_logger.debug("'%s' doesn't belong to '%s' - no check for sanitize errors", self.command[0], build_path) - + yatest_logger.debug("'%s' doesn't belong to '%s' - no check for sanitize errors", self.command[0], build_path) + def on_timeout_gen_coredump(exec_obj, _): """ @@ -413,38 +413,38 @@ def on_timeout_gen_coredump(exec_obj, _): pass -def execute( - command, check_exit_code=True, - shell=False, timeout=None, - cwd=None, env=None, - stdin=None, stdout=None, stderr=None, - creationflags=0, wait=True, - process_progress_listener=None, close_fds=False, - collect_cores=True, check_sanitizer=True, preexec_fn=None, on_timeout=None, +def execute( + command, check_exit_code=True, + shell=False, timeout=None, + cwd=None, env=None, + stdin=None, stdout=None, stderr=None, + creationflags=0, wait=True, + process_progress_listener=None, close_fds=False, + collect_cores=True, check_sanitizer=True, preexec_fn=None, on_timeout=None, executor=_Execution, -): - """ - Executes a command - :param command: command: can be a list of arguments or a string - :param check_exit_code: will raise ExecutionError if the command exits with non zero code - :param shell: use shell to run the command - :param timeout: execution timeout - :param cwd: working directory - :param env: command environment - :param stdin: command stdin - :param stdout: command stdout - :param stderr: command stderr - :param creationflags: command creation flags - :param wait: should wait until the command finishes - :param process_progress_listener=object that is polled while execution is in progress - :param close_fds: subrpocess.Popen close_fds args +): + """ + Executes a command + :param command: command: can be a list of arguments or a string + :param check_exit_code: will raise ExecutionError if the command exits with non zero code + :param shell: use shell to run the command + :param timeout: execution timeout + :param cwd: working directory + :param env: command environment + :param stdin: command stdin + :param stdout: command stdout + :param stderr: command stderr + :param creationflags: command creation flags + :param wait: should wait until the command finishes + :param process_progress_listener=object that is polled while execution is in progress + :param close_fds: subrpocess.Popen close_fds args :param collect_cores: recover core dump files if shell == False :param check_sanitizer: raise ExecutionError if stderr contains sanitize errors - :param preexec_fn: subrpocess.Popen preexec_fn arg - :param on_timeout: on_timeout(<execution object>, <timeout value>) callback - + :param preexec_fn: subrpocess.Popen preexec_fn arg + :param on_timeout: on_timeout(<execution object>, <timeout value>) callback + :return _Execution: Execution object - """ + """ if env is None: env = os.environ.copy() else: @@ -462,9 +462,9 @@ def execute( if var not in env and var in os.environ: env[var] = os.environ[var] - if not wait and timeout is not None: - raise ValueError("Incompatible arguments 'timeout' and wait=False") - + if not wait and timeout is not None: + raise ValueError("Incompatible arguments 'timeout' and wait=False") + # if subprocess.PIPE in [stdout, stderr]: # raise ValueError("Don't use pipe to obtain stream data - it may leads to the deadlock") @@ -481,15 +481,15 @@ def execute( is_pipe = stream == subprocess.PIPE return stream, not is_pipe - # to be able to have stdout/stderr and track the process time execution, we don't use subprocess.PIPE, - # as it can cause processes hangs, but use tempfiles instead + # to be able to have stdout/stderr and track the process time execution, we don't use subprocess.PIPE, + # as it can cause processes hangs, but use tempfiles instead out_file, user_stdout = get_out_stream(stdout, 'out') err_file, user_stderr = get_out_stream(stderr, 'err') - in_file = stdin - - if shell and type(command) == list: - command = " ".join(command) - + in_file = stdin + + if shell and type(command) == list: + command = " ".join(command) + if shell: collect_cores = False check_sanitizer = False @@ -516,7 +516,7 @@ def execute( name = "PIPE" if stdin == subprocess.PIPE else stdin.name yatest_logger.debug("Executing '%s' with input '%s' in '%s'", command, name, cwd) else: - yatest_logger.debug("Executing '%s' in '%s'", command, cwd) + yatest_logger.debug("Executing '%s' in '%s'", command, cwd) # XXX started = time.time() @@ -528,11 +528,11 @@ def execute( yatest_logger.debug("Command pid: %s", process.pid) res = executor(command, process, out_file, err_file, process_progress_listener, cwd, collect_cores, check_sanitizer, started, user_stdout=user_stdout, user_stderr=user_stderr) - if wait: - res.wait(check_exit_code, timeout, on_timeout) - return res - - + if wait: + res.wait(check_exit_code, timeout, on_timeout) + return res + + def _get_command_output_file(cmd, ext): parts = [get_command_name(cmd)] if 'YA_RETRY_INDEX' in os.environ: @@ -570,74 +570,74 @@ def py_execute( creationflags=0, wait=True, process_progress_listener=None, close_fds=False ): - """ - Executes a command with the arcadia python - :param command: command to pass to python - :param check_exit_code: will raise ExecutionError if the command exits with non zero code - :param shell: use shell to run the command - :param timeout: execution timeout - :param cwd: working directory - :param env: command environment - :param stdin: command stdin - :param stdout: command stdout - :param stderr: command stderr - :param creationflags: command creation flags - :param wait: should wait until the command finishes - :param process_progress_listener=object that is polled while execution is in progress + """ + Executes a command with the arcadia python + :param command: command to pass to python + :param check_exit_code: will raise ExecutionError if the command exits with non zero code + :param shell: use shell to run the command + :param timeout: execution timeout + :param cwd: working directory + :param env: command environment + :param stdin: command stdin + :param stdout: command stdout + :param stderr: command stderr + :param creationflags: command creation flags + :param wait: should wait until the command finishes + :param process_progress_listener=object that is polled while execution is in progress :return _Execution: Execution object - """ + """ if isinstance(command, six.string_types): - command = [command] - command = [runtime.python_path()] + command - if shell: - command = " ".join(command) - return execute(**locals()) - - -def _format_error(error): - return truncate(error, MAX_MESSAGE_LEN) - - -def wait_for(check_function, timeout, fail_message="", sleep_time=1.0, on_check_condition=None): - """ - Tries to execute `check_function` for `timeout` seconds. - Continue until function returns nonfalse value. - If function doesn't return nonfalse value for `timeout` seconds - OperationTimeoutException is raised. - Return first nonfalse result returned by `checkFunction`. - """ - if sleep_time <= 0: - raise ValueError("Incorrect sleep time value {}".format(sleep_time)) - if timeout < 0: - raise ValueError("Incorrect timeout value {}".format(timeout)) - start = time.time() - while start + timeout > time.time(): - if on_check_condition: - on_check_condition() - - res = check_function() - if res: - return res - time.sleep(sleep_time) - - message = "{} second(s) wait timeout has expired".format(timeout) - if fail_message: - message += ": {}".format(fail_message) + command = [command] + command = [runtime.python_path()] + command + if shell: + command = " ".join(command) + return execute(**locals()) + + +def _format_error(error): + return truncate(error, MAX_MESSAGE_LEN) + + +def wait_for(check_function, timeout, fail_message="", sleep_time=1.0, on_check_condition=None): + """ + Tries to execute `check_function` for `timeout` seconds. + Continue until function returns nonfalse value. + If function doesn't return nonfalse value for `timeout` seconds + OperationTimeoutException is raised. + Return first nonfalse result returned by `checkFunction`. + """ + if sleep_time <= 0: + raise ValueError("Incorrect sleep time value {}".format(sleep_time)) + if timeout < 0: + raise ValueError("Incorrect timeout value {}".format(timeout)) + start = time.time() + while start + timeout > time.time(): + if on_check_condition: + on_check_condition() + + res = check_function() + if res: + return res + time.sleep(sleep_time) + + message = "{} second(s) wait timeout has expired".format(timeout) + if fail_message: + message += ": {}".format(fail_message) raise TimeoutError(truncate(message, MAX_MESSAGE_LEN)) - + def _kill_process_tree(process_pid, target_pid_signal=None): - """ - Kills child processes, req. Note that psutil should be installed - @param process_pid: parent id to search for descendants - """ - yatest_logger.debug("Killing process %s", process_pid) - if os.name == 'nt': - _win_kill_process_tree(process_pid) - else: + """ + Kills child processes, req. Note that psutil should be installed + @param process_pid: parent id to search for descendants + """ + yatest_logger.debug("Killing process %s", process_pid) + if os.name == 'nt': + _win_kill_process_tree(process_pid) + else: _nix_kill_process_tree(process_pid, target_pid_signal) - - + + def _nix_get_proc_children(pid): try: cmd = ["pgrep", "-P", str(pid)] @@ -654,21 +654,21 @@ def _get_binname(pid): def _nix_kill_process_tree(pid, target_pid_signal=None): - """ - Kills the process tree. - """ + """ + Kills the process tree. + """ yatest_logger.debug("Killing process tree for pid {} (bin:'{}')".format(pid, _get_binname(pid))) - - def try_to_send_signal(pid, sig): - try: - os.kill(pid, sig) + + def try_to_send_signal(pid, sig): + try: + os.kill(pid, sig) yatest_logger.debug("Sent signal %d to the pid %d", sig, pid) - except Exception as exc: + except Exception as exc: yatest_logger.debug("Error while sending signal {sig} to pid {pid}: {error}".format(sig=sig, pid=pid, error=str(exc))) - - try_to_send_signal(pid, signal.SIGSTOP) # Stop the process to prevent it from starting any child processes. - - # Get the child process PID list. + + try_to_send_signal(pid, signal.SIGSTOP) # Stop the process to prevent it from starting any child processes. + + # Get the child process PID list. child_pids = _nix_get_proc_children(pid) # Stop the child processes. for child_pid in child_pids: @@ -679,16 +679,16 @@ def _nix_kill_process_tree(pid, target_pid_signal=None): # Skip the error and continue killing. yatest_logger.debug("Killing child pid {pid} failed: {error}".format(pid=child_pid, error=e)) continue - + try_to_send_signal(pid, target_pid_signal or signal.SIGKILL) # Kill the root process. - - # sometimes on freebsd sigkill cannot kill the process and either sigkill or sigcont should be sent - # https://www.mail-archive.com/freebsd-hackers@freebsd.org/msg159646.html - try_to_send_signal(pid, signal.SIGCONT) - - -def _win_kill_process_tree(pid): - subprocess.call(['taskkill', '/F', '/T', '/PID', str(pid)]) + + # sometimes on freebsd sigkill cannot kill the process and either sigkill or sigcont should be sent + # https://www.mail-archive.com/freebsd-hackers@freebsd.org/msg159646.html + try_to_send_signal(pid, signal.SIGCONT) + + +def _win_kill_process_tree(pid): + subprocess.call(['taskkill', '/F', '/T', '/PID', str(pid)]) def _run_readelf(binary_path): |