[Kimchi-devel] [PATCH V3] add a synchronous function with timeout to execute command

Rodrigo Trujillo rodrigo.trujillo at linux.vnet.ibm.com
Tue Jan 14 16:31:34 UTC 2014


Reviewed-by: Rodrigo Trujillo <rodrigo.trujillo at linux.vnet.ibm.com>

On 01/14/2014 11:03 AM, shaohef at linux.vnet.ibm.com wrote:
> From: ShaoHe Feng <shaohef at linux.vnet.ibm.com>
>
> We need a common function to execute shell command.
> We also need timeout when execute shell command.
>
> A threading.Timer is used to send signal.SIGKILL to kill the command
> when timeout.
>
> run a command with timeout as follow.
> $ PYTHONPATH=src python -c '
> from kimchi import utils
> print utils.run_command(["dd", "if=/dev/zero", "of=/dev/null"], 1.5)
> '
>
> Signed-off-by: Royce Lv <lvroyce at linux.vnet.ibm.com>
> Signed-off-by: ShaoHe Feng <shaohef at linux.vnet.ibm.com>
> ---
>   src/kimchi/exception.py |  4 ++++
>   src/kimchi/utils.py     | 58 +++++++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 62 insertions(+)
>
> diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py
> index bff0a18..a37015b 100644
> --- a/src/kimchi/exception.py
> +++ b/src/kimchi/exception.py
> @@ -43,3 +43,7 @@ class InvalidOperation(Exception):
>
>   class IsoFormatError(Exception):
>       pass
> +
> +
> +class TimeoutExpired(Exception):
> +    pass
> diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py
> index af245c6..331da91 100644
> --- a/src/kimchi/utils.py
> +++ b/src/kimchi/utils.py
> @@ -23,13 +23,16 @@
>
>   import cherrypy
>   import os
> +import subprocess
>   import urllib2
>
>
>   from cherrypy.lib.reprconf import Parser
> +from kimchi.exception import TimeoutExpired
>
>
>   from kimchi import config
> +from threading import Timer
>
>
>   kimchi_log = cherrypy.log.error_log
> @@ -96,3 +99,58 @@ def check_url_path(path):
>           return False
>
>       return True
> +
> +
> +def run_command(cmd, timeout=None):
> +    """
> +    cmd is a sequence of command arguments.
> +    timeout is a float number in seconds.
> +    timeout default value is None, means command run without timeout.
> +    """
> +    def kill_proc(proc, timeout_flag):
> +        try:
> +            proc.kill()
> +        except OSError:
> +            pass
> +        else:
> +            timeout_flag[0] = True
> +
> +    proc = None
> +    timer = None
> +    timeout_flag = [False]
> +
> +    try:
> +        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
> +                                stderr=subprocess.PIPE)
> +        if timeout is not None:
> +            timer = Timer(timeout, kill_proc, [proc, timeout_flag])
> +            timer.setDaemon(True)
> +            timer.start()
> +
> +        out, error = proc.communicate()
> +        kimchi_log.debug("Run command: '%s'", " ".join(cmd))
> +
> +        if out or error:
> +            kimchi_log.debug("out:\n %s\nerror:\n %s", out, error)
> +
> +        if timeout_flag[0]:
> +            msg = ("subprocess is killed by signal.SIGKILL for "
> +                   "timeout %s seconds" % timeout)
> +            kimchi_log.error(msg)
> +            raise TimeoutExpired(msg)
> +
> +        return out, error, proc.returncode
> +    except TimeoutExpired:
> +        raise
> +    except Exception as e:
> +        msg = "Failed to run command: %s." % " ".join(cmd)
> +        msg = msg if proc is None else msg + "\n  error code: %s."
> +        kimchi_log.error("%s\n  %s", msg, e)
> +
> +        if proc:
> +            return out, error, proc.returncode
> +        else:
> +            return None, None, None
> +    finally:
> +        if timer and not timeout_flag[0]:
> +            timer.cancel()




More information about the Kimchi-devel mailing list