aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndré Erdmann <dywi@mailerd.de>2014-12-16 00:00:17 +0100
committerAndré Erdmann <dywi@mailerd.de>2014-12-16 00:00:17 +0100
commitf195588254e4f1516ec024973ebba3660622c3fa (patch)
tree28f573850a9b34dc20a5b91b998599dcab77274e
parentroverlay/util/common: doc typo (diff)
downloadR_overlay-f195588254e4f1516ec024973ebba3660622c3fa.tar.gz
R_overlay-f195588254e4f1516ec024973ebba3660622c3fa.tar.bz2
R_overlay-f195588254e4f1516ec024973ebba3660622c3fa.zip
add subprocess helper module
-rw-r--r--roverlay/tools/subproc.py157
1 files changed, 157 insertions, 0 deletions
diff --git a/roverlay/tools/subproc.py b/roverlay/tools/subproc.py
new file mode 100644
index 0000000..a62601f
--- /dev/null
+++ b/roverlay/tools/subproc.py
@@ -0,0 +1,157 @@
+# R overlay -- tools, subprocess helpers
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import os
+import subprocess
+import sys
+
+__all__ = [
+ 'get_subproc_devnull',
+ 'subproc_send_term', 'subproc_send_kill',
+ 'stop_subprocess', 'gracefully_stop_subprocess',
+ 'create_subprocess', 'run_subprocess'
+]
+
+# python >= 3.3 has ProcessLookupError, use more generic exception otherwise
+if sys.hexversion >= 0x3030000:
+ _ProcessLookupError = ProcessLookupError
+else:
+ _ProcessLookupError = OSError
+
+# python >= 3.3:
+if hasattr ( subprocess, 'DEVNULL' ):
+ def get_subproc_devnull(mode=None):
+ """Returns a devnull object suitable
+ for passing it as stdin/stdout/stderr to subprocess.Popen().
+
+ Python 3.3 and later variant: uses subprocess.DEVNULL
+
+ arguments:
+ * mode -- ignored
+ """
+ return subprocess.DEVNULL
+else:
+ def get_subproc_devnull(mode='a+'):
+ """Returns a devnull object suitable
+ for passing it as stdin/stdout/stderr to subprocess.Popen().
+
+ Python 3.2 and earlier variant: opens os.devnull
+
+ arguments:
+ * mode -- mode for open(). Defaults to read/append
+ """
+ return open ( os.devnull, mode )
+# --
+
+def _proc_catch_lookup_err ( func ):
+ def wrapped ( proc ):
+ try:
+ func ( proc )
+ except _ProcessLookupError:
+ return False
+ return True
+
+ return wrapped
+
+@_proc_catch_lookup_err
+def subproc_send_term ( proc ):
+ proc.terminate()
+
+@_proc_catch_lookup_err
+def subproc_send_kill ( proc ):
+ proc.kill()
+
+
+def stop_subprocess ( proc, kill_timeout_cs=10 ):
+ """Terminates or kills a subprocess created by subprocess.Popen().
+
+ Sends SIGTERM first and sends SIGKILL if the process is still alive after
+ the given timeout.
+
+ Returns: None
+
+ arguments:
+ * proc -- subprocess
+ * kill_timeout_cs -- max time to wait after terminate() before sending a
+ kill signal (in centiseconds). Should be an int.
+ Defaults to 10 (= 1s).
+ """
+ if not subproc_send_term ( proc ):
+ return
+
+ try:
+ for k in range ( kill_timeout_cs ):
+ if proc.poll() is not None:
+ return
+ time.sleep ( 0.1 )
+ except:
+ subproc_send_kill ( proc )
+ else:
+ subproc_send_kill ( proc )
+# --- end of stop_subprocess (...) ---
+
+def gracefully_stop_subprocess ( proc, **kill_kwargs ):
+ try:
+ if subproc_send_term ( proc ):
+ proc.communicate()
+ except:
+ stop_subprocess ( proc, **kwargs )
+ raise
+
+def create_subprocess ( cmdv, **kwargs ):
+ """subprocess.Popen() wrapper that redirects stdin/stdout/stderr to
+ devnull or to a pipe if set to False/True.
+
+ Returns: subprocess
+
+ arguments:
+ * cmdv --
+ * **kwargs --
+ """
+ devnull_obj = None
+
+ for key in { 'stdin', 'stdout', 'stderr' }:
+ if key not in kwargs:
+ pass
+ elif kwargs [key] is True:
+ kwargs [key] = subprocess.PIPE
+
+ elif kwargs [key] is False:
+ if devnull_obj is None:
+ devnull_obj = get_subproc_devnull()
+ assert devnull_obj is not None
+
+ kwargs [key] = devnull_obj
+ # else don't care
+ # --
+
+ return subprocess.Popen ( cmdv, **kwargs )
+# --- end of create_subprocess (...) ---
+
+def run_subprocess ( cmdv, kill_timeout_cs=10, **kwargs ):
+ """Calls create_subprocess() and waits for the process to exit.
+ Catches exceptions and terminates/kills the process in that case
+
+ Returns: 2-tuple ( subprocess, output )
+
+ arguments:
+ * cmdv --
+ * kill_timeout_cs -- time to wait after SIGTERM before sending SIGKILL
+ (in centiseconds), see stop_subprocess() for details
+ Defaults to 10.
+ * **kwargs --
+ """
+ proc = None
+ try:
+ proc = create_subprocess ( cmdv, **kwargs )
+ output = proc.communicate()
+ except:
+ if proc is not None:
+ stop_subprocess ( proc, kill_timeout_cs=kill_timeout_cs )
+ raise
+
+ return ( proc, output )
+# --- end of run_subprocess (...) ---