Make run_command process communication smarter

The run_command module function will now poll stdout/stderr for
data rather than using the builtin command communicate(), which can
hang under certain circumstances.

Fixes #7452
Fixes #7748
Fixes #8163
pull/8435/merge
James Cammarata 10 years ago
parent 74cbeb1292
commit 5d0bb33ede

@ -53,6 +53,7 @@ import sys
import syslog
import types
import time
import select
import shutil
import stat
import tempfile
@ -1163,7 +1164,7 @@ class AnsibleModule(object):
# rename might not preserve context
self.set_context_if_different(dest, context, False)
def run_command(self, args, check_rc=False, close_fds=False, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False):
def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False):
'''
Execute a command, returns rc, stdout, and stderr.
args is the command to run
@ -1237,7 +1238,7 @@ class AnsibleModule(object):
executable=executable,
shell=shell,
close_fds=close_fds,
stdin= st_in,
stdin=st_in,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
@ -1260,10 +1261,48 @@ class AnsibleModule(object):
try:
cmd = subprocess.Popen(args, **kwargs)
# the communication logic here is essentially taken from that
# of the _communicate() function in ssh.py
stdout = ''
stderr = ''
rpipes = [cmd.stdout, cmd.stderr]
if data:
if not binary_data:
data += '\n'
out, err = cmd.communicate(input=data)
cmd.stdin.write(data)
cmd.stdin.close()
while True:
rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
if cmd.stdout in rfd:
dat = os.read(cmd.stdout.fileno(), 9000)
stdout += dat
if dat == '':
rpipes.remove(cmd.stdout)
if cmd.stderr in rfd:
dat = os.read(cmd.stderr.fileno(), 9000)
stderr += dat
if dat == '':
rpipes.remove(cmd.stderr)
# only break out if no pipes are left to read or
# the pipes are completely read and
# the process is terminated
if (not rpipes or not rfd) and cmd.poll() is not None:
break
# No pipes are left to read but process is not yet terminated
# Only then it is safe to wait for the process to be finished
# NOTE: Actually cmd.poll() is always None here if rpipes is empty
elif not rpipes and cmd.poll() == None:
cmd.wait()
# The process is terminated. Since no pipes to read from are
# left, there is no need to call select() again.
break
cmd.stdout.close()
cmd.stderr.close()
rc = cmd.returncode
except (OSError, IOError), e:
self.fail_json(rc=e.errno, msg=str(e), cmd=clean_args)
@ -1271,13 +1310,13 @@ class AnsibleModule(object):
self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)
if rc != 0 and check_rc:
msg = err.rstrip()
self.fail_json(cmd=clean_args, rc=rc, stdout=out, stderr=err, msg=msg)
msg = stderr.rstrip()
self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg)
# reset the pwd
os.chdir(prev_dir)
return (rc, out, err)
return (rc, stdout, stderr)
def append_to_file(self, filename, str):
filename = os.path.expandvars(os.path.expanduser(filename))

Loading…
Cancel
Save