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
This commit is contained in:
parent
74cbeb1292
commit
5d0bb33ede
1 changed files with 45 additions and 6 deletions
|
@ -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…
Reference in a new issue