Commit c010b729 authored by Jonathan Michalon's avatar Jonathan Michalon

Merge branch 'subprocess-timeout' into 'master'

Subprocess Timeout

Adds support for timeout in subprocess communication for Python < 3.3, and use it in `Check.exec_with_timeout()`.
parents 75eba0ac e3fe6f81
from subprocess import Popen, PIPE
import concurrent.futures import concurrent.futures
from time import sleep from time import sleep
from IPy import IP from IPy import IP
from sys import hexversion as sys_hexversion
# Import or implement Popen() with timeout support
if sys_hexversion >= 0x03030000:
# on Python 3.3 Popen() supports timeout, we have nothing to do
from subprocess import TimeoutExpired, Popen, PIPE
# on Python < 3.3, implement timeout with a thread
from threading import Thread
import subprocess
from subprocess import PIPE
class TimeoutExpired(subprocess.SubprocessError):
def __init__(self, args, timeout=None, output=None):
self.cmd, self.timeout, self.output = args, timeout, output
def __str__(self):
return 'Command %s timed out after %g seconds' % (self.args,
class Popen(subprocess.Popen):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._out = None
self._err = None
self._exc = None
self._thread = None
def _communicate_thread(self, input=None):
self._out, self._err = super().communicate(input=input)
except Exception as ex:
self._exc = ex
def communicate(self, input=None, timeout=None):
if timeout is None and not self._thread:
# without a timeout, default implementation is fine
return super().communicate(input=input)
elif self._thread and not self._thread.is_alive():
# if this is a second call and the thread finished, return
# the existing result
if self._exc:
raise self._exc
return self._out, self._err
# otherwise, implement the timeout
if not self._thread:
self._thread = Thread(target=self._communicate_thread,
if self._thread.is_alive():
raise TimeoutExpired(self.args, timeout)
if self._exc:
raise self._exc
return self._out, self._err
class Check(object): class Check(object):
...@@ -37,19 +94,18 @@ class Check(object): ...@@ -37,19 +94,18 @@ class Check(object):
return self.ok return self.ok
def exec_with_timeout(self, command, timeout=2): def exec_with_timeout(self, command, timeout=2):
self.errmsg = ''
p = Popen(command, stdout=PIPE, stderr=PIPE) p = Popen(command, stdout=PIPE, stderr=PIPE)
out, err = p.communicate() try:
# with 3.3 we got timeout… out, err = p.communicate(timeout=timeout)
# try: except TimeoutExpired:
# out, err = p.communicate(timeout=timeout) p.kill()
# except TimeoutExpired: out, err = p.communicate()
# p.kill() self.errmsg += "Operation timed out\n"
# out, err = p.communicate() if p.returncode != 0:
ret = p.poll() self.errmsg += "stdout: " + str(out) + '\n' + \
if ret != 0: "stderr: " + str(err) + '\n'
self.errmsg = "stdout: " + str(out) + '\n' + \ return p.returncode == 0
"stderr: " + str(err) + '\n'
return ret == 0
def exec_by_ip_family(self, addr, v4command, v6command): def exec_by_ip_family(self, addr, v4command, v6command):
ipv = IP(addr).version() ipv = IP(addr).version()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment