summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2017-01-26 21:55:41 +0200
committerLars Wirzenius <liw@liw.fi>2017-01-26 21:55:41 +0200
commit0e03de07ff197e7bfb06cd6cc7daef7e4bc96681 (patch)
tree3d3333572346335812bbe3ce0b84f966f0777963
parentddbec5d0874305787ba3a0f1ba9852699df01751 (diff)
downloadcliapp-0e03de07ff197e7bfb06cd6cc7daef7e4bc96681.tar.gz
Add an output timeout to runcmd, and a callback
-rw-r--r--cliapp/runcmd.py23
-rw-r--r--example_runcmd.py30
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'