From 3983638176e3f5b5584a2d1ea7ece59faa128908 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Thu, 10 Mar 2016 03:24:20 +0100 Subject: Non-Blocking read of subprocess output Some deamons does not close stdout (and the other fds) during forking. So our direct child will die, but Popen.communicate will block until the daemon dies. We now read the output non-blocking and stop reading it, if we get SIGCHLD and our direct child is dead. Because we now handle SIGCHLD by ourselfs, we need to ensure to call Popen.wait so that the kernel can cleanup our child and we do not get defunct processes. --- spline-startup | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/spline-startup b/spline-startup index e85dfc4..6012b38 100755 --- a/spline-startup +++ b/spline-startup @@ -2,8 +2,10 @@ from __future__ import print_function import argparse +import fcntl import os import pwd +import select import signal import sys import syslog @@ -41,6 +43,51 @@ def _get_users(config): return users +class Executor(Popen): + def __init__(self, *args, **kwargs): + self.running = True + + self.orig_signal = signal.signal(signal.SIGCHLD, self._signal) + return super(Executor, self).__init__(*args, **kwargs) + + def readlines(self): + fl = fcntl.fcntl(self.stdout, fcntl.F_GETFL) + fcntl.fcntl(self.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK) + + buffer = '' + while self.running: + try: + ready = select.select([self.stdout], [], [], 0) + except select.error: + pass + + if ready[0] != []: + buffer += self.stdout.read(8192) + lines = buffer.splitlines() + for line in lines[:-1]: + yield line + if len(lines) > 0: + buffer = lines[-1] + + self.wait() + + try: + buffer += self.stdout.read(8192) + except: + pass + + for line in buffer.splitlines(): + yield line + + def wait(self): + result = super(Executor, self).wait() + signal.signal(signal.SIGCHLD, self.orig_signal) + return result + + def _signal(self, *args): + self.running = False + + class SplineStartup(object): def __init__(self): @@ -104,12 +151,9 @@ class SplineStartup(object): def _call(self, cmd): self._pinfo('Calling: %s' % ' '.join(cmd)) if not self.options.dry_run: - proc = Popen(cmd, stdout=PIPE, stderr=STDOUT) - output, _ = proc.communicate() - output = output.strip() - if output != '': - for line in output.split('\n'): - self._pinfo(line) + proc = Executor(cmd, stdout=PIPE, stderr=STDOUT) + for line in proc.readlines(): + self._pinfo(line) return proc.returncode else: return 0 -- cgit v1.2.3-1-g7c22