diff options
author | Lars Wirzenius <liw@liw.fi> | 2017-01-26 21:55:41 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2017-01-26 21:55:41 +0200 |
commit | 0e03de07ff197e7bfb06cd6cc7daef7e4bc96681 (patch) | |
tree | 3d3333572346335812bbe3ce0b84f966f0777963 | |
parent | ddbec5d0874305787ba3a0f1ba9852699df01751 (diff) | |
download | cliapp-0e03de07ff197e7bfb06cd6cc7daef7e4bc96681.tar.gz |
Add an output timeout to runcmd, and a callback
-rw-r--r-- | cliapp/runcmd.py | 23 | ||||
-rw-r--r-- | example_runcmd.py | 30 |
2 files changed, 48 insertions, 5 deletions
diff --git a/cliapp/runcmd.py b/cliapp/runcmd.py index f434030..32fb0e0 100644 --- a/cliapp/runcmd.py +++ b/cliapp/runcmd.py @@ -22,6 +22,7 @@ import logging import os import select import subprocess +import time import cliapp @@ -95,6 +96,7 @@ def runcmd_unchecked(argv, *argvs, **kwargs): stdout_callback = pop_kwarg('stdout_callback', noop) stderr_callback = pop_kwarg('stderr_callback', noop) output_timeout = pop_kwarg('output_timeout', None) + timeout_callback = pop_kwarg('timeout_callback', None) try: pipeline = _build_pipeline(argvs, @@ -105,7 +107,7 @@ def runcmd_unchecked(argv, *argvs, **kwargs): return _run_pipeline(pipeline, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, stdout_callback, stderr_callback, - output_timeout) + output_timeout, timeout_callback) except OSError, e: # pragma: no cover if e.errno == errno.ENOENT and e.filename is None: e.filename = argv[0] @@ -162,7 +164,8 @@ def _build_pipeline(argvs, pipe_stdin, pipe_stdout, pipe_stderr, kwargs): def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, - stdout_callback, stderr_callback, output_timeout): + stdout_callback, stderr_callback, output_timeout, + timeout_callback): stdout_eof = False stderr_eof = False @@ -170,6 +173,8 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, err = [] pos = 0 io_size = 1024 + latest_output = time.time() + timeout = False def set_nonblocking(fd): flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) @@ -195,7 +200,7 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, return True # pragma: no cover return False - while still_running(): + while not timeout and still_running(): rlist = [] if not stdout_eof and pipe_stdout == subprocess.PIPE: rlist.append(procs[-1].stdout) @@ -208,7 +213,7 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, if rlist or wlist: try: - r, w, _ = select.select(rlist, wlist, []) + r, w, _ = select.select(rlist, wlist, [], output_timeout) except select.error as e: # pragma: no cover if e.args[0] == errno.EINTR: break @@ -216,6 +221,11 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, else: break # Let's not busywait waiting for processes to die. + now = time.time() + time_since_output = now - latest_output + timeout = (output_timeout is not None and + time_since_output >= output_timeout) + if procs[0].stdin in w and pos < len(feed_stdin): data = feed_stdin[pos:pos + io_size] procs[0].stdin.write(data) @@ -243,11 +253,14 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr, else: stderr_eof = True - while still_running(): + while not timeout and still_running(): for p in procs: if p.returncode is None: p.wait() + if timeout and timeout_callback: # pragma: no cover + timeout_callback() + errorcodes = [p.returncode for p in procs if p.returncode != 0] or [0] return errorcodes[-1], ''.join(out), ''.join(err) diff --git a/example_runcmd.py b/example_runcmd.py new file mode 100644 index 0000000..7eaf431 --- /dev/null +++ b/example_runcmd.py @@ -0,0 +1,30 @@ +# Copyright (C) 2016 Lars Wirzenius +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# =*= License: GPL-2+ =*= + + +import cliapp + + +def cb(): + print 'callback called' + + +print 'sleeping 10' +r = cliapp.runcmd_unchecked(['sleep', '10'], output_timeout=2, timeout_callback=cb) +print repr(r) +print 'done' |