From 4ec9393759f446168e45a6a148f01368ad6ea417 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 12 Oct 2013 18:20:32 +0200 Subject: [PATCH] Fixed Bug where long outputs to stderr during build process caused a hang --- fdroidserver/build.py | 68 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/fdroidserver/build.py b/fdroidserver/build.py index dbdfa087..e10b3b13 100644 --- a/fdroidserver/build.py +++ b/fdroidserver/build.py @@ -27,12 +27,36 @@ import tarfile import traceback import time import json +import Queue +import threading from optparse import OptionParser import common from common import BuildException from common import VCSException +class AsynchronousFileReader(threading.Thread): + ''' + Helper class to implement asynchronous reading of a file + in a separate thread. Pushes read lines on a queue to + be consumed in another thread. + ''' + + def __init__(self, fd, queue): + assert isinstance(queue, Queue.Queue) + assert callable(fd.readline) + threading.Thread.__init__(self) + self._fd = fd + self._queue = queue + + def run(self): + '''The body of the tread: read lines and put them on the queue.''' + for line in iter(self._fd.readline, ''): + self._queue.put(line) + + def eof(self): + '''Check whether there is no more content to expect.''' + return not self.is_alive() and self._queue.empty() def get_builder_vm_id(): vd = os.path.join('builder', '.vagrant') @@ -448,20 +472,36 @@ def build_local(app, thisbuild, vcs, build_dir, output_dir, srclib_dir, extlib_d p = subprocess.Popen(['bash', '-x', '-c', cmd], cwd=root_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - for line in iter(p.stdout.readline, ''): - if verbose: - # Output directly to console - sys.stdout.write(line) - sys.stdout.flush() - else: - output += line - for line in iter(p.stderr.readline, ''): - if verbose: - # Output directly to console - sys.stdout.write(line) - sys.stdout.flush() - else: - error += line + + stdout_queue = Queue.Queue() + stdout_reader = AsynchronousFileReader(p.stdout, stdout_queue) + stdout_reader.start() + stderr_queue = Queue.Queue() + stderr_reader = AsynchronousFileReader(p.stderr, stderr_queue) + stderr_reader.start() + + # Check the queues if we received some output (until there is nothing more to get). + while not stdout_reader.eof() or not stderr_reader.eof(): + # Show what we received from standard output. + while not stdout_queue.empty(): + line = stdout_queue.get() + if verbose: + # Output directly to console + sys.stdout.write(line) + sys.stdout.flush() + else: + error += line + + # Show what we received from standard error. + while not stderr_queue.empty(): + line = stderr_queue.get() + if verbose: + # Output directly to console + sys.stdout.write(line) + sys.stdout.flush() + else: + error += line + p.communicate() if p.returncode != 0: raise BuildException("Error running build command for %s:%s" %