From 9c233417e0bcf2837a619d7b21b42ed093dc8552 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Wed, 19 Dec 2007 11:11:05 +0000 Subject: [PATCH] Make utils.RunCmd() deal with interleaved stdout/stderr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, RunCmd is written with the assumption that programs will have a small stderr output, therefore we read the child's stdout (which can be big, so we don't want to block the child) and then the stderr (which is small, so it shouldn't block). However, with the βgnt-cluster verify-disksβ command, we ourselves generate heavy stderr, therefore we break the ganeti-watcher which runs the verify-disks via utils.RunCmd. This patch turns the RunCmd command into an poll-based one, which means any kind of interleaved output by a child on stdout/stderr will be handled correctly. Of course, since the output is buffered in memory, there are other ways to break RunCmd(). But at least this should fix the common case. Reviewed-by: hansmi --- lib/utils.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/utils.py b/lib/utils.py index a85ebb048..d5fe36ea9 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -36,6 +36,10 @@ import shutil import errno import pwd import itertools +import select +import fcntl + +from cStringIO import StringIO from ganeti import logger from ganeti import errors @@ -222,6 +226,7 @@ def RunCmd(cmd): shell = True env = os.environ.copy() env["LC_ALL"] = "C" + poller = select.poll() child = subprocess.Popen(cmd, shell=shell, stderr=subprocess.PIPE, stdout=subprocess.PIPE, @@ -229,8 +234,35 @@ def RunCmd(cmd): close_fds=True, env=env) child.stdin.close() - out = child.stdout.read() - err = child.stderr.read() + poller.register(child.stdout, select.POLLIN) + poller.register(child.stderr, select.POLLIN) + out = StringIO() + err = StringIO() + fdmap = { + child.stdout.fileno(): (out, child.stdout), + child.stderr.fileno(): (err, child.stderr), + } + for fd in fdmap: + status = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK) + + while fdmap: + for fd, event in poller.poll(): + if event & select.POLLIN or event & select.POLLPRI: + data = fdmap[fd][1].read() + # no data from read signifies EOF (the same as POLLHUP) + if not data: + poller.unregister(fd) + del fdmap[fd] + continue + fdmap[fd][0].write(data) + if (event & select.POLLNVAL or event & select.POLLHUP or + event & select.POLLERR): + poller.unregister(fd) + del fdmap[fd] + + out = out.getvalue() + err = err.getvalue() status = child.wait() if status >= 0: -- GitLab